当前位置: 首页 > news >正文

Linux之基础IO

目录

一、理解 "文件"

1.1、狭义理解

1.2、广义理解

1.3、文件操作的归类认知

1.4、系统角度

二、回顾C语言接口

2.1、打开文件

2.2、写文件

2.3、读文件

2.4、stdin & stdout & stderr

2.6、打开文件的方式

三、系统文件I/O

3.1、一种传递标志位的方法

3.2、写文件

3.3、读文件

3.4、接口介绍

3.5、open函数返回值

3.6、文件描述符

3.6.1、0 & 1 & 2

3.6.2、文件描述符的分配规则

3.6.3、重定向

3.6.4、使用dup2系统调用

3.6.5、在minishell中添加重定向功能

四、理解一切皆文件

五、缓冲区

5.1、什么是缓冲区

5.2、Linux中文件的内核级缓冲区

5.3、文件的用户级缓冲区

5.4、为什么要引入缓冲区机制

5.5、缓冲类型

5.5、FIFE

5.6、简单设计libc库


一、理解 "文件"

1.1、狭义理解

  • ⽂件在磁盘⾥。
  • 磁盘是永久性存储介质,因此⽂件在磁盘上的存储是永久性的。
  • 磁盘是外设(即是输出设备也是输⼊设备)。
  • 磁盘上的⽂件本质是对⽂件的所有操作,都是对外设的输⼊和输出,简称 IO。

1.2、广义理解

  • Linux 下⼀切皆⽂件(键盘、显⽰器、⽹卡、磁盘…… 这些都是抽象化的过程)。

1.3、文件操作的归类认知

  • 对于 0KB 的空⽂件也是占⽤磁盘空间的。
  • ⽂件是⽂件属性(元数据)和⽂件内容的集合(⽂件 = 属性(元数据)+ 内容)。 
  • 所有的⽂件操作本质是⽂件内容操作和⽂件属性操作。
  • 访问一个文件之前,都必须先打开它。这是因为访问一个文件的时候,本质是一个进程在访问它,而进程是在内存中的,文件没有被打开时是存储在磁盘中的,所以要想访问文件,需要先将它的内容和属性都加载到内存中,这个加载的过程叫做打开文件。
  • 实际场景中,会有很多文件被加载到内存中,所以操作系统需要对文件进行管理。如何管理呢?答案是:先描述,在组织。即:内核中,文件 = 文件的内核数据结构 + 文件的内容。

1.4、系统角度

  • 对⽂件的操作本质是进程对⽂件的操作。
  • 磁盘的管理者是操作系统。
  • ⽂件的读写本质不是通过 C 语⾔ / C++ 的库函数来操作的(这些库函数只是为用户提供⽅便),⽽是通过⽂件相关的系统调⽤接⼝来实现的。

二、回顾C语言接口

2.1、打开文件

示例代码:

  1 #include<stdio.h>2 3 int main()4 {5   FILE* fp = fopen("log.txt","w");6   if(fp == NULL)7   {8     perror(fopen:);9     return 1;10   }11 12   //......文件操作                                                                                                                                                              13   fclose(fp);                                                                                                                       14   return 0;                    15 }

打开的 log.txt ⽂件在哪个路径下?答案是默认在程序的当前路径下,那系统怎么知道程序的当前路径在哪⾥呢?可以使⽤ ls /proc/[进程id] -l 命令查看当前正在运⾏进程的信息:

其中:

  • cwd:指向当前进程运⾏⽬录的⼀个符号链接。
  • exe:指向启动当前进程的可执⾏⽂件(完整路径)的符号链接。

打开⽂件,本质是进程打开,所以,进程知道⾃⼰在哪⾥,即便⽂件不带路径,进程也知道。由此OS 就能知道要创建的⽂件放在哪⾥。

2.2、写文件

示例代码:

  1 #include<stdio.h>  2   3 int main()  4 {  5   FILE* fp = fopen("log.txt","w");  6   if(fp == NULL)  7   {  8     perror("fopen:");  9     return 1;  10   }  11   12   char buffer[1024];  13   const char* message = "hello,bit";  14   int i = 0;  15   while(i < 10)  16   {  17     snprintf(buffer, sizeof(buffer), "%s:%d\n", message, i);  18     fputs(buffer,fp);  19     i++;  20   }  21   fclose(fp);22   return 0;  23 }  

以 w 形式打开的文件,如果文件不存在,会自动创建该文件并打开,且每次打开文件时都会先对该文件原有内容进行清空。

2.3、读文件

示例代码:

 26 #include <stdio.h>27 #include <string.h>28 29 int main()30 {31    FILE *fp = fopen("log.txt", "r");32    if(!fp)33    {34      printf("fopen error!\n");35      return 1;36    }37    char buf[1024];38    const char *msg = "hello bit!\n";39    while(1)40    {41      //注意返回值和参数,此处有坑,仔细查看man 手册关于该函数的说明                                                                                                             42      ssize_t s = fread(buf, 1, strlen(msg), fp);43      if(s > 0)44      {     45         buf[s] = 0;46         printf("%s", buf);47      }48      if(feof(fp))49      {50         break;51      }52    }53    fclose(fp);54    return 0;55 }

2.4、stdin & stdout & stderr

  • C程序默认会打开三个输⼊输出流,分别是stdin,stdout,stderr。
  • 仔细观察发现,这三个流的类型都是FILE*,即fopen返回值类型,⽂件指针。

#include<stdio.h>

extern FILE *stdin;

extern FILE *stdout;

extern FILE *stderr;

示例代码:

 26 #include <stdio.h>27 #include <string.h>28 29 int main()30 {31     printf("hello wrold\n");32     fputs("aaaa", stdout);33     fwrite("bbbb", 1, 4, stdout);34     fprintf(stdout, "cccc");35 36     return 0;37 }

2.6、打开文件的方式

r                 Open text file for reading.

         The stream is positioned at the beginning of the file.

r+              Open for reading and writing.

         The stream is positioned at the beginning of the file.

w                Truncate(缩短) file to zero length or create text file for writing.

         The stream is positioned at the beginning of the file.

w+              Open for reading and writing.

         The file is created if it does not exist, otherwise it is truncated.

         The stream is positioned at the beginning of the file.

a                 Open for appending (writing at end of file).

         The file is created if it does not exist.

        The stream is positioned at the end of the file.

a+               Open for reading and appending (writing at end of file).

         The file is created if it does not exist. The initial file position for reading is at the         beginning of the file, but output is always appended to the end of the file.

三、系统文件I/O

打开⽂件的⽅式不仅仅是fopen,ifstream等流式,语⾔层的⽅案,其实系统才是打开⽂件最底层的⽅案。不过,在学习系统⽂件IO之前,先要了解下如何给函数传递标志位,该⽅法在系统⽂件IO接⼝中会使⽤到:

3.1、一种传递标志位的方法

示例代码:(通过按位或传入标志位,按位与检测标志位)

  1 #include<stdio.h>2 3 #define ONE (1<<0)4 #define TWO (1<<1)5 #define THREE (1<<2)6 #define FOUR (1<<3)7 #define FIVE (1<<4)8 9 void PrintTest(int flags)10 {11     if(flags & ONE)12     {13         printf("one\n");14     }15     if(flags & TWO)16     {17         printf("two\n");18     }19     if(flags & THREE)                  20     {                                  21         printf("three\n");22     }                                  23     if(flags & FOUR)                   24     {                                                                                                                                                                           25         printf("four\n");                 26     }                                          27     if(flags & FIVE)                   28     {                                          29         printf("five\n");              30     }                                          31 }   32                                                33 int main()                             34 {                                              35     printf("===========================\n");  36     PrintTest(ONE);                            37     printf("===========================\n");  38     PrintTest(TWO);                           39     printf("===========================\n");40     PrintTest(THREE);41     printf("===========================\n");42     PrintTest(ONE | THREE);43     printf("===========================\n");44     PrintTest(ONE | TWO | THREE);45     printf("===========================\n");46     PrintTest(ONE | TWO | THREE | FOUR);47     printf("===========================\n");48     return 0;49 }

操作⽂件,除了上面的C接⼝(当然,C++也有接⼝,其他语⾔也有),我们还可以采⽤系统接⼝来进⾏⽂件访问。

3.2、写文件

示例代码:

  1 #include<stdio.h>2 #include<sys/types.h>3 #include<sys/stat.h>4 #include<fcntl.h>5 #include<unistd.h>6 #include<string.h>7 8 int main()9 {10   umask(0);11   int fd = open("myfile", O_WRONLY | O_CREAT, 0644);12  13   if(fd < 0)14   {15     perror("open");16     return 1;17   }18   19   int count = 5;20   const char *msg = "hello bit!\n";21   int len = strlen(msg);22   while(count--)23   {24     write(fd, msg, len);25     //fd:open返回值,文件描述符,即向哪里写入     26     //msg:缓冲区地址,即写入谁 27     //len: 本次读取,期望写多少个字节的数据。28     //返回值:实际写了多少字节数据 29   }30   close(fd);                                                                                                                                                                    31   return 0;                        32 } 

3.3、读文件

示例代码:

  1 #include<stdio.h>2 #include<sys/types.h>3 #include<sys/stat.h>4 #include<fcntl.h>5 #include<unistd.h>6 #include<string.h>7 8 int main()9 {10   int fd = open("myfile", O_RDONLY);11  12   if(fd < 0)13   {14     perror("open");15     return 1;16   }17   18   const char *msg = "hello bit!\n";19   char buf[1024];20  21   while(1)22   {23     ssize_t s = read(fd, buf, strlen(msg));//参数类似于write                                                                                                                    24     if(s > 0)                                                           25     {                                                                   26       printf("%s", buf);                                                27     }                                                                   28     else                                                                29     {                                                                   30       break;                                                            31     }                                                                   32   }                                                                     33                                                                         34   close(fd);                                                            35   return 0;                                                             36 }

3.4、接口介绍

open:

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

int open(const char *pathname, int flags);

int open(const char *pathname, int flags, mode_t mode);

pathname: 要打开或创建的⽬标⽂件

flags: 打开⽂件时,可以传⼊多个参数选项,⽤下⾯的⼀个或者多个常量进⾏“或”运算,构成

flags。

参数:         O_RDONLY: 只读打开

                 O_WRONLY: 只写打开

                 O_RDWR : 读,写打开

                                这三个常量,必须指定⼀个且只能指定⼀个

                 O_CREAT : 若⽂件不存在,则创建它。需要使⽤mode选项,来指明新⽂件的访问权限

                 O_APPEND: 追加写

                 O_TRUNC:打开文件时清空文件

返回值:

                成功:新打开的⽂件描述符

                失败:-1

  • mode_t理解:新建文件时的权限大小。但是这个权限并不等于文件的权限,文件的最终权限是mode_t & ~umask(权限掩码)。我们可以在打开文件前将umask设置为0,这样mode_t就直接等于新建文件的最终权限。而且这并不会影响到其他进程,因为每个进程都有自己独立的umask。
  • 如果我们打开文件时,O_APPEND,O_TRUNC选项都没有使用,那么默认文件内容不会清空,也不会从文件末尾追加写入,而是从文件开头开始写,原有位置的内容会被依次覆盖。
  • open 函数具体使⽤哪个,和具体应⽤场景相关,如⽬标⽂件不存在,需要open创建,则第三个参数表⽰创建⽂件的默认权限,否则,使⽤两个参数的open。write read close lseek ,类⽐C⽂件相关接⼝。

3.5、open函数返回值

open函数是系统调用,它的返回值是一个文件描述符。在认识返回值之前,先来认识⼀下两个概念:系统调⽤ 和 库函数

  • 上⾯的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数 (libc)。
  • ⽽ open close read write lseek 都属于系统提供的接⼝,称之为系统调⽤接⼝。

系统调⽤接⼝和库函数的关系,⼀⽬了然。所以,可以认为, f 系列的函数(fopen,fread.....),都是对系统调⽤的封装,⽅便⼆次开发。

3.6、文件描述符

  • 通过对open函数的学习,我们知道了⽂件描述符就是⼀个⼩的整数

3.6.1、0 & 1 & 2

  • Linux进程默认情况下会有3个缺省打开的⽂件描述符,分别是标准输⼊0,标准输出1,标准错误2。
  • 0,1,2对应的物理设备⼀般是:键盘,显⽰器,显⽰器。

所以输⼊输出还可以采⽤如下⽅式:

  1 #include<stdio.h>  2 #include<sys/types.h>  3 #include<sys/stat.h>  4 #include<fcntl.h>  5 #include<unistd.h>  6 #include<string.h>  7 8 int main()9 {10     //直接使用文件描述符读取键盘输入内容11     char buffer[128];12     ssize_t s = read(0, buffer, sizeof(buffer));13     if(s > 0)14     {15         buffer[s-1] = 0;16         printf("%s\n", buffer);17     }18     19     //直接使用文件描述符向显示器输出内容                                                                                                                                        20     const char *message = "hello write\n";                                                                               21     write(1, message, strlen(message));                                                                                  22                                                                                                                          23   return 0;                                                                                                              24 }  

⽽现在知道,⽂件描述符就是从0开始的⼩整数。当我们打开⽂件时,操作系统在内存中要创建相应的数据结构来描述⽬标⽂件。于是就有了file结构体。表⽰⼀个已经打开的⽂件对象。⽽进程执⾏open系统调⽤,所以必须让进程和⽂件关联起来。每个进程都有⼀个指针*files,指向⼀张表files_struct,该表最重要的部分就是包含⼀个指针数组,每个元素都是⼀个指向打开⽂件的指针!所以,本质上,⽂件描述符就是该数组的下标。所以,只要拿着⽂件描述符,就可以找到对应的⽂件。

C语言也是通过文件描述符的方式对文件进行操作的(其实所有语言都是),只是进行了封装,所以我们感受不到而已。C语言中 fopen 函数返回的是FILE* 类型,指向的FILE类型是个结构体,所以文件描述符被封装到了该结构体中。我们可以打印一下看看,示例代码:

  1 #include<stdio.h>  2 #include<sys/types.h>  3 #include<sys/stat.h>  4 #include<fcntl.h>  5 #include<unistd.h>  6 #include<string.h>  7 8 9 int main()10 {11     //打印标准输入,输出,错误流的文件描述符12     printf("stdin: %d\n", stdin->_fileno);13     printf("stdout: %d\n", stdout->_fileno);14     printf("stderr: %d\n", stderr->_fileno);15 16     //打印自己打开的文件的文件描述符17     FILE* fp = fopen("log.txt", "w");18     printf("fp: %d\n", fp->_fileno);19 20   return 0;21 }

3.6.2、文件描述符的分配规则

示例代码一:

  1 #include<stdio.h>2 #include<sys/types.h>3 #include<sys/stat.h>4 #include<fcntl.h>5 #include<unistd.h>6 7 int main()8 {9   int fd1 = open("log1.txt",O_WRONLY | O_CREAT | O_APPEND,0666);10   int fd2 = open("log2.txt",O_WRONLY | O_CREAT | O_APPEND,0666);11   int fd3 = open("log3.txt",O_WRONLY | O_CREAT | O_APPEND,0666);12   int fd4 = open("log4.txt",O_WRONLY | O_CREAT | O_APPEND,0666);13 14   printf("fd1: %d\n",fd1);15   printf("fd2: %d\n",fd2);16   printf("fd3: %d\n",fd3);17   printf("fd4: %d\n",fd4);                                                                                                                                                      18 19   close(fd1);20   close(fd2);21   close(fd3);22   close(fd4);23   return 0;24 }

效果:

示例代码二:(关闭文件描述符 0 )

  1 #include<stdio.h>2 #include<sys/types.h>3 #include<sys/stat.h>4 #include<fcntl.h>5 #include<unistd.h>6 7 int main()8 {9   close(0);                                                                                                                                                                     10 11   int fd1 = open("log1.txt",O_WRONLY | O_CREAT | O_APPEND,0666);12   int fd2 = open("log2.txt",O_WRONLY | O_CREAT | O_APPEND,0666);13   int fd3 = open("log3.txt",O_WRONLY | O_CREAT | O_APPEND,0666);14   int fd4 = open("log4.txt",O_WRONLY | O_CREAT | O_APPEND,0666);15 16   printf("fd1: %d\n",fd1);17   printf("fd2: %d\n",fd2);18   printf("fd3: %d\n",fd3);19   printf("fd4: %d\n",fd4);20 21   close(fd1);22   close(fd2);23   close(fd3);24   close(fd4);25   return 0;26 }

效果:

示例代码三:(关闭文件描述符 2 )

  1 #include<stdio.h>2 #include<sys/types.h>3 #include<sys/stat.h>4 #include<fcntl.h>5 #include<unistd.h>6 7 int main()8 {9   close(2);                                                                                                                                                                     10 11   int fd1 = open("log1.txt",O_WRONLY | O_CREAT | O_APPEND,0666);12   int fd2 = open("log2.txt",O_WRONLY | O_CREAT | O_APPEND,0666);13   int fd3 = open("log3.txt",O_WRONLY | O_CREAT | O_APPEND,0666);14   int fd4 = open("log4.txt",O_WRONLY | O_CREAT | O_APPEND,0666);15 16   printf("fd1: %d\n",fd1);17   printf("fd2: %d\n",fd2);18   printf("fd3: %d\n",fd3);19   printf("fd4: %d\n",fd4);20 21   close(fd1);22   close(fd2);23   close(fd3);24   close(fd4);25   return 0;26 }

效果:

从上面三段示例代码和对应的结果可以看出,⽂件描述符的分配规则:在files_struct数组当中,找到当前没有被使⽤的最⼩的⼀个下标,作为新的⽂件描述符。

3.6.3、重定向

示例代码:

  1 #include<stdio.h>2 #include<sys/types.h>3 #include<sys/stat.h>4 #include<fcntl.h>5 #include<unistd.h>6 7 int main()8 {9   close(1);10 11   int fd1 = open("log1.txt",O_WRONLY | O_CREAT | O_APPEND,0666);12   int fd2 = open("log2.txt",O_WRONLY | O_CREAT | O_APPEND,0666);13   int fd3 = open("log3.txt",O_WRONLY | O_CREAT | O_APPEND,0666);14   int fd4 = open("log4.txt",O_WRONLY | O_CREAT | O_APPEND,0666);15 16   printf("fd1: %d\n",fd1);17   printf("fd2: %d\n",fd2);18   printf("fd3: %d\n",fd3);19   printf("fd4: %d\n",fd4);20 21   fflush(stdout);                                                                                                                                                               22 23   close(fd1);24   close(fd2);25   close(fd3);26   close(fd4);27   return 0;28 }

效果:

此时,我们发现,本来应该输出到显⽰器上的内容,输出到了⽂件 log1.txt 当中,其中,它的文件描述符 fd=1。这种现象叫做输出重定向。常⻅的重定向有: > ,>> ,< 。

重定向的本质就是修改文件描述符表的某一个下标中存储的内容。如下图:

注意:

  • 程序替换操作不会影响替换前重定向的文件描述符。
  • 文件描述符的生命周期随进程,当进程结束时,该进程所打开的文件描述符会自动释放。

3.6.4、使用dup2系统调用

原型函数:

#include<unistd.h>

int dup2(int oldfd, int newfd);

作用:将下标为 oldfd 的文件描述符表中的内容复制到 newfd,即让newfd 重定向到 oldfd 所指向的文件/资源。 

注意:它会关闭因为重定向而导致没有文件描述符指向的文件,但是不会关闭 oldfd,所以最终重定向的文件会被两个文件描述符指向。如果不想这样可以在重定向后关闭 oldfd。

输出重定向示例代码:

  1 #include<stdio.h>2 #include<sys/types.h>3 #include<sys/stat.h>4 #include<fcntl.h>5 #include<unistd.h>6 #include<string.h>7 8 int main()9 {10   int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);11 12   dup2(fd,1);13 14   write(1, "hello 1\n", 8);15 16   printf("hello,fd: %d\n", fd);17   fprintf(stdout,"hello,fd:%d,\n", fd);18   fputs("hello world\n", stdout);19                               20   const char* message = "hello fwrite\n";21   fwrite(message, 1, strlen(message), stdout);22   23   return 0;24 }

printf是C库当中的IO函数,⼀般往 stdout 中输出,但是stdout底层访问⽂件的时候,找的还是fd:1,但此时,fd:1下标所表⽰内容,已经变成了 log.txt 的地址,不再是显⽰器⽂件的地址,所以,输出的任何消息都会往⽂件中写⼊,进⽽完成输出重定向。那追加和输⼊重定向如何完成呢?

输入重定向示例代码:

  1 #include<stdio.h>2 #include<sys/types.h>3 #include<sys/stat.h>4 #include<fcntl.h>5 #include<unistd.h>6 #include<string.h>7 8 int main()9 {10   int fd = open("log.txt", O_RDONLY);11 12   dup2(fd, 0);13 14   char buffer[2048];15   size_t s = read(0, buffer, sizeof(buffer));16   if(s > 0)17   {18     buffer[s] = 0;19     printf("stdin redir:\n%s\n",buffer);                                                                                                                                        20   }2122   return 0;23 }

3.6.5、在minishell中添加重定向功能

完整代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>using namespace std;const int basesize = 1024;
const int argvnum = 64;
const int envnum = 64;
// 全局的命令行参数表
char *gargv[argvnum];
int gargc = 0;// 全局的变量
int lastcode = 0;// 我的系统的环境变量
char *genv[envnum];// 全局的当前shell工作路径 
char pwd[basesize];
char pwdenv[basesize];// 全局变量与重定向有关
#define NoneRedir   0
#define InputRedir  1
#define OutputRedir 2
#define AppRedir    3int redir = NoneRedir;
char *filename = nullptr;// "    "file.txt
#define TrimSpace(pos) do{\while(isspace(*pos)){\pos++;\}\
}while(0)string GetUserName()
{string name = getenv("USER");return name.empty() ? "None" : name;
}string GetHostName()
{string hostname = getenv("HOSTNAME");return hostname.empty() ? "None" : hostname;
}string GetPwd()
{if(nullptr == getcwd(pwd, sizeof(pwd))) return "None";snprintf(pwdenv, sizeof(pwdenv),"PWD=%s", pwd);putenv(pwdenv); // PWD=XXXreturn pwd;//string pwd = getenv("PWD");//return pwd.empty() ? "None" : pwd;
}string LastDir()
{string curr = GetPwd();if(curr == "/" || curr == "None") return curr;// /home/whb/XXXsize_t pos = curr.rfind("/");if(pos == std::string::npos) return curr;return curr.substr(pos+1);
}string MakeCommandLine()
{// [whb@bite-alicloud myshell]$ char command_line[basesize];snprintf(command_line, basesize, "[%s@%s %s]# ",\GetUserName().c_str(), GetHostName().c_str(), LastDir().c_str());return command_line;
}void PrintCommandLine() // 1. 命令行提示符
{printf("%s", MakeCommandLine().c_str());fflush(stdout);
}bool GetCommandLine(char command_buffer[], int size)   // 2. 获取用户命令
{// 我们认为:我们要将用户输入的命令行,当做一个完整的字符串// "ls -a -l -n"char *result = fgets(command_buffer, size, stdin);if(!result){return false;}command_buffer[strlen(command_buffer)-1] = 0;if(strlen(command_buffer) == 0) return false;return true;
}void ResetCommandline()
{memset(gargv, 0, sizeof(gargv));gargc = 0;// 重定向redir = NoneRedir;filename = nullptr;
}void ParseRedir(char command_buffer[], int len)
{int end = len - 1;while(end >= 0){if(command_buffer[end] == '<'){redir = InputRedir;command_buffer[end] = 0;filename = &command_buffer[end] + 1;TrimSpace(filename);break;}else if(command_buffer[end] == '>'){if(command_buffer[end-1] == '>'){redir = AppRedir;command_buffer[end] = 0;command_buffer[end-1] = 0;filename = &command_buffer[end]+1;TrimSpace(filename);break;}else{redir = OutputRedir;command_buffer[end] = 0;filename = &command_buffer[end]+1;TrimSpace(filename);break;}}else{end--;}}
}void ParseCommand(char command_buffer[])
{// "ls -a -l -n"const char *sep = " ";gargv[gargc++] = strtok(command_buffer, sep);// =是刻意写的while((bool)(gargv[gargc++] = strtok(nullptr, sep)));gargc--;
}void ParseCommandLine(char command_buffer[], int len) // 3. 分析命令
{ResetCommandline();ParseRedir(command_buffer, len);ParseCommand(command_buffer);//printf("command start: %s\n", command_buffer);// "ls -a -l -n"// "ls -a -l -n" > file.txt// "ls -a -l -n" < file.txt// "ls -a -l -n" >> file.txt//printf("redir: %d\n", redir);//printf("filename: %s\n", filename);//printf("command end: %s\n", command_buffer);}void debug()
{printf("argc: %d\n", gargc);for(int i = 0; gargv[i]; i++){printf("argv[%d]: %s\n", i, gargv[i]);}
}//enum
//{
//    FILE_NOT_EXISTS = 1,
//    OPEN_FILE_ERROR,
//};void DoRedir()
{// 1. 重定向应该让子进程自己做!// 2. 程序替换会不会影响重定向?不会// 0. 先判断 && 重定向if(redir == InputRedir){if(filename){int fd = open(filename, O_RDONLY);if(fd < 0){exit(2);}dup2(fd, 0);}else{exit(1);}}else if(redir == OutputRedir){if(filename){int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);if(fd < 0){exit(4);}dup2(fd, 1);}else{exit(3);}}else if(redir == AppRedir){if(filename){int fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);if(fd < 0){exit(6);}dup2(fd, 1);}else{exit(5);}}else{// 没有重定向,Do Nothong!}
}// 在shell中
// 有些命令,必须由子进程来执行
// 有些命令,不能由子进程执行,要由shell自己执行 --- 内建命令 built command
bool ExecuteCommand()   // 4. 执行命令
{// 让子进程进行执行pid_t id = fork();if(id < 0) return false;if(id == 0){//子进程DoRedir();// 1. 执行命令execvpe(gargv[0], gargv, genv);// 2. 退出exit(7);}int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){if(WIFEXITED(status)){lastcode = WEXITSTATUS(status);}else{lastcode = 100;}return true;}return false;
}void AddEnv(const char *item)
{int index = 0;while(genv[index]){index++;}genv[index] = (char*)malloc(strlen(item)+1);strncpy(genv[index], item, strlen(item)+1);genv[++index] = nullptr;
}
// shell自己执行命令,本质是shell调用自己的函数
bool CheckAndExecBuiltCommand()
{if(strcmp(gargv[0], "cd") == 0){// 内建命令if(gargc == 2){chdir(gargv[1]);lastcode = 0;}else{lastcode = 1;}return true;}else if(strcmp(gargv[0], "export") == 0){// export也是内建命令if(gargc == 2){AddEnv(gargv[1]);lastcode = 0;}else{lastcode = 2;}return true;}else if(strcmp(gargv[0], "env") == 0){for(int i = 0; genv[i]; i++){printf("%s\n", genv[i]);}lastcode = 0;return true;}else if(strcmp(gargv[0], "echo") == 0){if(gargc == 2){// echo $?// echo $PATH// echo helloif(gargv[1][0] == '$'){if(gargv[1][1] == '?'){printf("%d\n", lastcode);lastcode = 0;}}else{printf("%s\n", gargv[1]);lastcode = 0;}}else{lastcode = 3;}return true;}return false;
}// 作为一个shell,获取环境变量应该从系统的配置来
// 我们今天就直接从父shell中获取环境变量
void InitEnv()
{extern char **environ;int index = 0;while(environ[index]){genv[index] = (char*)malloc(strlen(environ[index])+1);strncpy(genv[index], environ[index], strlen(environ[index])+1);index++;}genv[index] = nullptr;
}int main()
{InitEnv();char command_buffer[basesize];while(true){PrintCommandLine(); // 1. 命令行提示符// command_buffer -> outputif( !GetCommandLine(command_buffer, basesize) )   // 2. 获取用户命令{continue;}//printf("%s\n", command_buffer);//ls//"ls -a -b -c -d"->"ls" "-a" "-b" "-c" "-d"//"ls -a -b -c -d">hello.txt//"ls -a -b -c -d">>hello.txt//"ls -a -b -c -d"<hello.txtParseCommandLine(command_buffer, strlen(command_buffer)); // 3. 分析命令if ( CheckAndExecBuiltCommand() ){continue;}ExecuteCommand();   // 4. 执行命令}return 0;
}

四、理解一切皆文件

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

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

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

五、缓冲区

5.1、什么是缓冲区

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

5.2、Linux中文件的内核级缓冲区

在Linux操作系统中,用来管理内核中的文件的结构体(struct file)中除了有文件的相关属性,指向操作表(函数指针集合)的指针外,还有一个指向缓冲区的指针,这块缓冲区就是文件的内核级缓冲区。文件的读写操作都和这块缓冲区有关。当我们读取数据时,其实是操作系统先将文件的一部分数据先加载到这块内核级缓冲区中,然后相关的系统调用再从这块缓冲区中读取数据;当我们写入数据时,其实是先通过相关的系统调用将数据写到这块内核级缓冲区中,当满足一定条件时,比如:缓冲区满了,就会将缓冲区中的内容一次性全部刷新到磁盘中。

注意:内核级缓冲区何时刷新是由操作系统(OS)决定的。

5.3、文件的用户级缓冲区

文件除了内核级缓冲区,还有一个用户级缓冲区,因为内核级缓冲区需要调用系统调用进行写入或读取,而频繁的调用系统调用是有一定的成本的(时间和空间),为了提高效率,C语言又封装了一个用户级缓冲区,这块缓冲区就在 FIFE* 指向的 FIFE 结构体中维护,当满足一定条件时,用户级缓冲区的内容会通过系统调用一次性刷新到内核级缓冲区。C语言中封装的 fclose 函数在关闭文件的时候,会自动刷新用户级缓冲区。而系统调用 close 不会刷新用户级缓冲区。当一个进程退出的时候,也会自动刷新用户级缓冲区。

注意:

  • 系统调用内核级缓冲区写入或读取。
  • C语言库中封装的文件操作的接口是向用户级缓冲区写入或读取。

5.4、为什么要引入缓冲区机制

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

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

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

5.5、缓冲类型

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

  • 全缓冲区:这种缓冲⽅式要求填满整个缓冲区后才进⾏I/O系统调⽤操作。对于磁盘⽂件的操作通常使⽤全缓冲的⽅式访问。
  • ⾏缓冲区:在⾏缓冲情况下,当在输⼊和输出中遇到换⾏符时,标准I/O库函数将会执⾏系统调⽤操作。当所操作的流涉及⼀个终端时(例如标准输⼊和标准输出),使⽤⾏缓冲⽅式。因为标准 I/O库每⾏的缓冲区⻓度是固定的,所以只要填满了缓冲区,即使还没有遇到换⾏符,也会执⾏ I/O系统调⽤操作,默认⾏缓冲区的⼤⼩为1024。
  • ⽆缓冲区:⽆缓冲区是指标准I/O库不对字符进⾏缓存,直接调⽤系统调⽤。标准出错流stderr通常是不带缓冲区的,这使得出错信息能够尽快地显⽰出来。

注意:普通文件默认是缓冲区写满,在刷新;显示器文件默认是行刷新。

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

  • 缓冲区满时;
  • 执⾏flush语句;

示例代码:

  1 #include<stdio.h>2 #include<unistd.h>3 #include<sys/types.h>4 #include<sys/stat.h>5 #include<fcntl.h>6 7 int main()8 {9   close(1);10   int fd = open("log.txt",O_CREAT | O_WRONLY | O_APPEND,0666);11 12   printf("hello,world\n");13 14   close(fd);                                                                                                                                                                    15 16   return 0;17 }

解释:上面代码关闭了 1 号文件描述符,在重新打开一个文件,这样新打开的文件就占据了 1 号文件描述符。这段代码的运行结果是显示器中没有任何输出,这是因为 1 号文件描述符(标准输出流)重定向到了 log.txt; log.txt 文件中也没有任何内容,这是因为原本 1 号文件描述符指向显示器文件,显示器文件默认是行刷新,所以遇到 '\n' 会刷新,但是现在重定向到了普通文件,普通文件默认是缓冲区写满再刷新,所以即使有 '\n' 也不会刷新,当进程退出时,会刷新该用户级缓冲区,但是进程结束之前已经通过系统调用 close 关闭了该文件,所以内容没有被刷新进文件。这里想要将内容刷新到文件中,可以使用下面三种方式中的任意一种:去掉 close 语句,不主动关闭文件;替换close 为 fclose;使用 fflush 强制刷新。

5.5、FIFE

  • 因为IO相关函数与系统调⽤接⼝对应,并且库函数封装系统调⽤,所以本质上,访问⽂件都是通过fd访问的。
  • 所以C库当中的FILE结构体内部,必定封装了fd。

示例代码:

#include <stdio.h>
#include <string.h>
#include <unistd.h>int main()
{// C库函数printf(" hello printf\n");fprintf(stdout, " hello fprintf\n");const char *message = " hello fwrite\n";fwrite(message, 1, strlen(message), stdout);// 系统调用const char *w = " hello write\n ";write(1, w, strlen(w));// 创建子进程fork();return 0;
}

效果:

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

  • ⼀般C库函数写⼊⽂件时是全缓冲的,⽽写⼊显⽰器是⾏缓冲。
  • printf,fwrite 库函数自带的用户级缓冲区,当发⽣重定向到普通⽂件时,数据的缓冲⽅式由⾏缓冲变成了全缓冲。
  • 因此我们放在缓冲区中的数据,就不会被⽴即刷新,甚⾄fork之后也不会被刷新。
  • 但是进程退出之后,会统⼀刷新,写⼊⽂件当中。
  • 但是fork的时候,⽗⼦数据会发⽣写时拷⻉,所以当你⽗进程准备刷新的时候,⼦进程也就有了同样的⼀份数据,随即产⽣两份数据。父子进程分别将自己那份数据刷新到文件中。
  • write 没有变化,说明它没有所谓的用户级缓冲。

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

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

5.6、简单设计libc库

my_stdio.h:

#pragma once#define SIZE 1024#define FLUSH_NONE 0
#define FLUSH_LINE 1
#define FLUSH_FULL 2struct 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:

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

注:如果想将我们自己写的库给别人用,我们可以将 .c 文件编译为 .o 文件,将头文件和 .o文件给别人,别人只需要包含我们的头文件就可以使用库中的方法了,最后生成可执行时将我们的.o文件和他自己的代码生成的.o文件进行链接即可。

相关文章:

Linux之基础IO

目录 一、理解 "文件" 1.1、狭义理解 1.2、广义理解 1.3、文件操作的归类认知 1.4、系统角度 二、回顾C语言接口 2.1、打开文件 2.2、写文件 2.3、读文件 2.4、stdin & stdout & stderr 2.6、打开文件的方式 三、系统文件I/O 3.1、一种传递标志…...

上位机知识篇---涂鸦智能云平台

文章目录 前言 前言 本文简单介绍了涂鸦智能云平台。...

InfluxDB 3 Core + Java 11 + Spring Boot:打造高效物联网数据平台

一、 引言&#xff1a;为什么选择InfluxDB 3&#xff1f; 项目背景&#xff1a; 在我们的隧道风机监控系统中&#xff0c;实时数据的采集、存储和高效查询是至关重要的核心需求。风机运行产生的振动、倾角、电流、温度等参数是典型的时序数据&#xff0c;具有高并发写入、数据…...

Kubernetes控制平面组件:Kubelet详解(七):容器网络接口 CNI

云原生学习路线导航页&#xff08;持续更新中&#xff09; kubernetes学习系列快捷链接 Kubernetes架构原则和对象设计&#xff08;一&#xff09;Kubernetes架构原则和对象设计&#xff08;二&#xff09;Kubernetes架构原则和对象设计&#xff08;三&#xff09;Kubernetes控…...

Pandas 构建并评价聚类模型② 第六章

构建并评价聚类模型 构建并评价聚类模型一、数据读取与准备&#xff08;代码6 - 6部分&#xff09;结果代码解析 二、Kmeans聚类&#xff08;代码6 - 6部分&#xff09;结果代码解析 三、数据降维可视化&#xff08;代码6 - 6部分&#xff09;结果代码解析 四、FMI评价&#xf…...

【simulink】IEEE33节点系统潮流分析模型

目录 主要内容 程序内容 2.1 33节点simulink模型一览 2.2 节点模型图 下载链接 主要内容 该仿真采用simulink模型对33节点网络进行模拟仿真&#xff0c;在simulink模型中定义了33节点系统的电阻、电抗、节点连接关系等参数&#xff0c;通过控制块来实现信号连接关系&…...

彻底解决docker代理配置与无法拉取镜像问题

为什么会有这篇文章? 博主在去年为部署dify研究了docker,最后也是成功部署,但是因为众所周知的原因,卡ziji脖子 ,所以期间遇到各种网络问题的报错,好在最后解决了. 但时隔一年,博主最近因为学习原因又一次使用docker,原本解决的问题却又没来由的出现,且和之前有很多不同(有时…...

Linux 安装 Unreal Engine

需要对在unreal engine官网进行绑定github账号&#xff0c;然后到unreal engine github仓库中进行下载对应的版本&#xff0c;并进行安装unreal engine官网 github地址...

tensorflow图像分类预测

tensorflow图像分类预测 CPU版本和GPU版本二选一 CPU版本 pip -m install --upgrade pippip install matplotlib pillow scikit-learnpip install tensorflow-intel2.18.0GPU版本 工具 miniconda 升级依赖库 conda update --all创建目录 mkdir gpu-tf进入目录 cd gpu-tf创建虚…...

C++数组详解:一维和多维数组的定义、初始化、访问与遍历

1. 引言 数组是C中最基础的数据结构之一&#xff0c;用于存储相同类型的元素的集合。它提供了高效的内存访问方式&#xff0c;适用于需要快速查找和遍历数据的场景。本文将全面介绍&#xff1a; 一维数组的定义、初始化与遍历多维数组&#xff08;如二维数组&#xff09;的定…...

linux下编写shell脚本一键编译源码

0 前言 进行linux应用层编程时&#xff0c;经常会使用重复的命令对源码进行编译&#xff0c;然后把编译生成的可执行文件拷贝到工作目录&#xff0c;操作非常繁琐且容易出错。本文编写一个简单的shell脚本一键编译源码。 1 linux下编写shell脚本一键编译源码 shell脚本如下&…...

安卓端互动娱乐房卡系统调试实录:从UI到协议的万字深拆(第一章)

前言&#xff1a;调房卡&#xff0c;不如修空调&#xff08;但更费脑&#xff09; 老实说&#xff0c;拿到这套安卓端互动组件源码的时候&#xff0c;我内心是拒绝的。不是因为它不好&#xff0c;而是太好了&#xff0c;目录规整、界面精美、逻辑还算清晰&#xff0c;唯一的问…...

【通用大模型】Serper API 详解:搜索引擎数据获取的核心工具

Serper API 详解&#xff1a;搜索引擎数据获取的核心工具 一、Serper API 的定义与核心功能二、技术架构与核心优势2.1 技术实现原理2.2 对比传统方案的突破性优势 三、典型应用场景与代码示例3.1 SEO 监控系统3.2 竞品广告分析 四、使用成本与配额策略五、开发者注意事项六、替…...

宝塔面板屏蔽垃圾搜索引擎蜘蛛和扫描工具的办法

首先进入宝塔面板&#xff0c;文件管理进入/www/server/nginx/conf目录&#xff0c;新建空白文件kill_bot.conf。然后将以下代码保存到当前文件中。 #禁止垃圾搜索引擎蜘蛛抓取if ($http_user_agent ~* "CheckMarkNetwork|Synapse|Nimbostratus-Bot|Dark|scraper|LMAO|Ha…...

【低成本STM32的T-BOX开发实战:高可靠的车联网解决方案】

基于STM32的车辆远程通信终端&#xff08;T-BOX&#xff09;开发实战&#xff1a;低成本高可靠的车联网解决方案 目录 引言&#xff1a;为什么需要T-BOX&#xff1f;系统总体设计&#xff1a;T-BOX的架构与核心功能硬件设计&#xff1a;STM32主控与关键模块解析 STM32F105VCT6…...

聚类算法K-means和Dbscan的对比

K-means和DBSCAN_dbscan和kmeans的区别-CSDN博客...

mysql的高可用

1. 环境准备 2台MySQL服务器&#xff08;node1: 192.168.1.101&#xff0c;node2: 192.168.1.102&#xff09;2台HAProxy Keepalived服务器&#xff08;haproxy1: 192.168.1.103&#xff0c;haproxy2: 192.168.1.104&#xff09;虚拟IP&#xff08;VIP: 192.168.1.100&#x…...

vue3 elementplus tabs切换实现

Tabs 标签页 | Element Plus <template><!-- editableTabsValue 是当前tab 的 name --><el-tabsv-model"editableTabsValue"type"border-card"editableedit"handleTabsEdit"><!-- 这个是标签面板 面板数据 遍历 editableT…...

printf在c语言中代表什么(非常详细)

在C语言中&#xff0c;有三个函数可以用来向控制台&#xff08;可以理解为显示器或者屏幕&#xff09;输出数据&#xff0c;它们分别是&#xff1a; 输出函数说明用法演示puts()只能输出字符串&#xff0c;并且输出结束后会自动换行puts("C language is great");put…...

Linux梦开始的地方

1.概率 经过C语言&#xff0c;数据结构&#xff0c;C的学习我们现在要开始学习Linux的学习了。我们学习Linux是从四部分来进行的&#xff1a; 1.Linux初识&#xff0c;Linux环境&#xff0c;Linux指令&#xff0c;Linux开发环境。 2.Linux系统。 3.Linux网络 4.MySQL Lin…...

关于机器学习的实际案例

以下是一些机器学习的实际案例&#xff1a; 营销与销售领域 - 推荐引擎&#xff1a;亚马逊、网飞等网站根据用户的品味、浏览历史和购物车历史进行推荐。 - 个性化营销&#xff1a;营销人员使用机器学习联系将产品留在购物车或退出网站的用户&#xff0c;根据客户兴趣定制营销…...

Kubernetes控制平面组件:Kubelet详解(五):切换docker运行时为containerd

云原生学习路线导航页&#xff08;持续更新中&#xff09; kubernetes学习系列快捷链接 Kubernetes架构原则和对象设计&#xff08;一&#xff09;Kubernetes架构原则和对象设计&#xff08;二&#xff09;Kubernetes架构原则和对象设计&#xff08;三&#xff09;Kubernetes控…...

<前端小白> 前端网页知识点总结

HTML 标签 1. 标题标签 h1到h6 2. 段落标签 p 3. 换行 br 水平线 hr 4. 加粗 strong 倾斜 em 下划线 ins 删除 del 5. 图像标签 img src-图像的位置 alt- 图片加载失败显示的文字 替换文本 title--- 鼠标放到图片上显示的文字 提示…...

【Linux驱动】Linux 按键驱动开发指南

Linux 按键驱动开发指南 1、按键驱动开发基础 1.1. 按键驱动类型 Linux下的按键驱动主要有两种实现方式&#xff1a; 输入子系统驱动&#xff1a;最常用&#xff0c;通过input子系统上报按键事件 字符设备驱动&#xff1a;较少用&#xff0c;需要自己实现文件操作接口 1.…...

AI日报 - 2025年05月19日

&#x1f31f; 今日概览 (60秒速览) ▎&#x1f916; 大模型前沿 | GPT-5传闻再起&#xff0c;将基于全新模型构建&#xff0c;与GPT-4彻底分离&#xff1b;Claude 3.7 Sonnet系统提示泄露&#xff0c;揭示其主动引导对话、多语言支持及安全新特性&#xff1b;研究指出直接复用…...

BUUCTF——ReadlezPHP

BUUCTF——ReadlezPHP 进入靶场 看了看框架和源码信息 没有什么可以利用的地方 爆破一下目录看看 结果只出来个index.php 看了一下Findsomthing 报了个路径 /time.php?source拼接访问一下 出了个php代码 <?php #error_reporting(0); class HelloPhp {public $a;pub…...

java集合相关的api-总结

简介 集合是存储数据的容器&#xff0c;集合相关的API提供了不同的数据结构&#xff0c;来满足不同的需求。这里是对常见集合API的使用场景和相关源码的一个总结&#xff0c;在实际开发中&#xff0c;如果不知道该选择什么集合&#xff0c;这篇文章也许可以参考一下。 集合相…...

FloodFill算法:洪水般的图像处理艺术

简单来说就是一场洪水&#xff08;雨水&#xff09;会把低洼的地方淹没 也就是一道题&#xff0c;你要找出所有为负数的连通块&#xff0c;对角线不能连通&#xff0c;所以上述图有两个 其实也很简单&#xff0c;就是你扫描的过程&#xff0c;发现一个负数&#xff0c;就以这…...

【开源分享】健康饮食管理系统(双端+论文)

&#x1f4bb;技术栈 前后端分离项目&#xff0c;PC双端&#xff08;管理端用户端&#xff09; 后端&#xff1a;Javaspringboot 前端&#xff1a;vue 数据库&#xff1a;mysql &#x1f4a1;运行效果图 1. 管理端&#xff1a; 2. 用户端&#xff1a; &#x1f4d5;源码获…...

【图像生成大模型】CogVideoX-5b:开启文本到视频生成的新纪元

CogVideoX-5b&#xff1a;开启文本到视频生成的新纪元 项目背景与目标模型架构与技术亮点项目运行方式与执行步骤环境准备模型加载与推理量化推理 执行报错与问题解决内存不足模型加载失败生成质量不佳 相关论文信息总结 在人工智能领域&#xff0c;文本到视频生成技术一直是研…...

C++学习:六个月从基础到就业——C++20:协程(Coroutines)

C学习&#xff1a;六个月从基础到就业——C20&#xff1a;协程(Coroutines) 本文是我C学习之旅系列的第五十篇技术文章&#xff0c;也是第三阶段"现代C特性"的第十二篇&#xff0c;继续介绍C20引入的新特性&#xff0c;本篇重点是协程(Coroutines)。查看完整系列目录…...

【DAY22】 复习日

内容来自浙大疏锦行python打卡训练营 浙大疏锦行 仔细回顾一下之前21天的内容 作业&#xff1a; 自行学习参考如何使用kaggle平台&#xff0c;写下使用注意点&#xff0c;并对下述比赛提交代码 kaggle泰坦里克号人员生还预测...

tauri2项目使用sidcar嵌入可执行文件并使用命令行调用

Sidecar 是 Tauri 框架中的一个功能&#xff0c;允许你将现有的命令行程序&#xff08;CLI&#xff09;打包并分发到你的 Tauri 应用程序中。以下是它的主要作用和用法。集成命令行工具&#xff1a;将现有的 CLI 程序无缝集成到你的 Tauri 应用中。跨平台分发&#xff1a;确保你…...

选择合适的AI模型:解析Trae编辑器中的多款模型及其应用场景

在当今数字化时代&#xff0c;人工智能技术飞速发展&#xff0c;各种AI模型层出不穷&#xff0c;为人们的工作和生活带来了极大的便利。Trae编辑器作为一款集成了多种先进AI模型的工具&#xff0c;为用户提供了丰富的选择&#xff0c;以满足不同场景下的多样化需求。本文将深入…...

超越想象:利用MetaGPT打造高效的AI协作环境

前言 在人工智能迅速发展的今天&#xff0c;如何让多个大语言模型&#xff08;LLM&#xff09;高效协同工作成为关键挑战。MetaGPT 作为一种创新的多智能体框架&#xff0c;成功模拟了一个真实软件公司的运作流程&#xff0c;实现了从需求分析到代码实现的全流程自动化&#x…...

BOM知识点

BOM&#xff08;Browser Object Model&#xff09;即浏览器对象模型&#xff0c;是用于访问和操作浏览器窗口的编程接口。以下是一些BOM的知识点总结&#xff1a; 核心对象 • window&#xff1a;BOM的核心对象&#xff0c;代表浏览器窗口。它也是全局对象&#xff0c;所有全…...

IDE/IoT/搭建物联网(LiteOS)集成开发环境,基于 LiteOS Studio + GCC + JLink

文章目录 概述LiteOS Studio不推荐&#xff1f;安装和使用手册呢?HCIP实验的源码呢&#xff1f; 软件和依赖安装软件下载软件安装插件安装依赖工具-方案2依赖工具-方案1 工程配置打开或新建工程板卡配置组件配置编译器配置-gcc工具链编译器配置-Makefile脚本其他配置编译完成 …...

常见的 HTTP 接口(请求方法)

一&#xff1a;GET 作用&#xff1a;从服务器获取资源&#xff08;查询数据&#xff09;。特点&#xff1a; 请求参数通过 URL 传递&#xff08;如https://api.example.com/users?id123&#xff09;&#xff0c;参数会显示在地址栏中。不修改服务器数据&#xff0c;属于幂等操…...

墨水屏显示模拟器程序解读

程序如下&#xff1a;出处https://github.com/tsl0922/EPD-nRF5?tabreadme-ov-file // GUI emulator for Windows // This code is a simple Windows GUI application that emulates the display of an e-paper device. #include <windows.h> #include <stdint.h>…...

【图像生成大模型】Step-Video-T2V:下一代文本到视频生成技术

Step-Video-T2V&#xff1a;下一代文本到视频生成技术 引言Step-Video-T2V 项目概述核心技术1. 视频变分自编码器&#xff08;Video-VAE&#xff09;2. 3D 全注意力扩散 Transformer&#xff08;DiT w/ 3D Full Attention&#xff09;3. 视频直接偏好优化&#xff08;Video-DPO…...

【Java学习笔记】【第一阶段项目实践】房屋出租系统(面向对象版本)

房屋出租系统&#xff08;面向对象版本&#xff09; 整体思想&#xff1a;采用数组存储房屋信息&#xff0c;深刻体会面向对象的好处和过程 一、实现需求 &#xff08;1&#xff09;用户层 系统菜单显示 提示用户输入对应的数字选择功能 各个功能界面操作提示&#xff08;底…...

18. 结合Selenium和YAML对页面继承对象PO的改造

18. 结合Selenium和YAML对页面继承对象PO的改造 一、架构改造核心思路 1.1 改造前后对比 #mermaid-svg-ziagMhNLS5fIFWrx {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-ziagMhNLS5fIFWrx .error-icon{fill:#5522…...

Vue-监听属性

监听属性 简单监听 点击切换名字&#xff0c;来回变更Tom/Jerry&#xff0c;输出 你好&#xff0c;Tom/Jerry 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><title>监听属性</title><!-- …...

AI写PPT可以用吗?我测试了3款AI写PPT工具,分享感受

上周五临下班&#xff0c;领导突然让我周末赶出一份季度营销报告 PPT&#xff0c;还要求周一晨会展示。看着空荡荡的 PPT 页面&#xff0c;我满心都是绝望 —— 周末不仅泡汤&#xff0c;搞不好还得熬夜到凌晨。好在同部门的前辈给我推荐了几款 AI 写 PPT 工具&#xff0c;没想…...

FreeSWITCH 简单图形化界面43 - 使用百度的unimrcp搞个智能话务台,用的在线的ASR和TTS

FreeSWITCH 简单图形化界面43 - 使用百度的unimrcp搞个智能话务台 0、一个fs的web配置界面预览1、安装unimrcp模块2、安装完成后&#xff0c;配置FreeSWITCH。2.1 有界面的配置2.1.1 mod_unimrcp模块配置2.1.2 mod_unimrcp客户端配置 2.2 无界面的配置 3、呼叫规则4、编写流程4…...

C 语言学习笔记(函数)

内容提要 函数 函数的概述函数的分类函数的定义形参和实参函数的返回值 函数 函数的概述 **函数&#xff1a;**实现一定功能的&#xff0c;独立的代码模块&#xff0c;对于函数的使用&#xff0c;一定是先定义&#xff0c;后使用。 使用函数的优势&#xff1a; ①我们可以…...

数据结构 -- 树形查找(二)平衡二叉树

平衡二叉树 定义 平衡二叉树&#xff08;AVL树&#xff09; – 树上的任意一点的左子树和右子树的高度之差不超过1 节点的平衡因子 左子树高-右子树高 平衡二叉树的结点的平衡因子的值只可能是-1、0、1 //平衡二叉树结点 typedef struct AVLNode{int key; //数据域int bal…...

day 29

类装饰器 类有修饰器&#xff0c;他的逻辑&#xff1a;接收一个类&#xff0c;返回一个修改后的类。例如 1. 添加新的方法或属性&#xff08;如示例中的 log 方法&#xff09;。 2. 修改原有方法&#xff08;如替换 __init__ 方法&#xff0c;添加日志&#xff09;。 3. 甚…...

Java 并发编程

黑马程序员深入学习Java并发编程 进程与线程 预备知识 java8&#xff0c;pom.xml <dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.10</version></depe…...

windows笔记本连接RKNN3588网络配置解析

这几天拿到了一块RKNN3588的板子,准备做视觉和Ros开发用,但是拿到后就蒙蔽了,不知道怎么ssh连到板子上去,更甚者不知道怎么配置网络让RKNN能够联网更新环境,这里记录一下整个过程。主要包括以下两个内容: 1.adb连接RKNN3588开发 2. 网口连接RKNN更新板子环境开发 adb连…...