Linux文件(系统)IO(含动静态库的链接操作)

news2024/12/23 5:05:49

文章目录

  • Linux文件(系统)IO(含动静态库的链接操作)
    • 1、C语言文件IO操作
    • 2、三个数据流stdin、stdout、stderr
    • 3、系统文件IO
      • 3.1、相关系统调用接口的使用
      • 3.2、文件描述符fd
      • 3.3、文件描述符的分配规则
      • 3.3、重定向
      • 3.4、自制shell加入重定向功能
    • 4、FILE
    • 5、文件系统
      • 5.1、inode
      • 5.2、硬链接(Hard Link)
      • 5.3、软链接(Symbolic Link)
    • 6、动态库和静态库
      • 6.1、静态库
      • 6.2、动态库

img

Linux文件(系统)IO(含动静态库的链接操作)

1、C语言文件IO操作

  • fopen打开文件,fclose关闭文件

  • fwrite操作(写文件)

fwrite函数从ptr里将nitems个大小为size字节的数据写进定stream

  • hello_w.c文件

    #include <stdio.h>                                                              
    #include <string.h>    
    
    int main(){    
        FILE* pf = fopen("myfile.txt","w");    
        if(!pf){    
            perror("fopen");    
            return 1;    
        }    
    
        const char* msg = "hello fopen\n";    
        int count = 5;    
        while(count--){    
            fwrite(msg,strlen(msg),1,pf);    
        }    
    
        fclose(pf);    
        return 0;    
    }
    

    这里我们将会在此路径得到一个写有5hello fopenmyfile.txt文件,如下gif:

  • fread操作(写文件)

fread 不会在字符串末尾添加 null 终止符(\0),fgets会。

fread函数从定stream里读取nitemssize字节大小的数据存到ptr

  • hello_r.c文件

    #include <stdio.h>
    #include <string.h>
    
    int main(){
        FILE* pf = fopen("myfile.txt","r");
        if(!pf){
            perror("fopen");
            return 1;
        }
    
        char buffer[1024];
    
        while(1){
            //fgets
            // 每次读1个字节,读1024次(文件结束就不读)
            size_t s = fread(buffer,1,sizeof(buffer),pf);
            if(s > 0){
                buffer[s] = 0;
                printf("%s",buffer);
            }
            if(feof(pf)){
                break;
            }
        }
        fclose(pf);
        return 0;
    }
    

    这里我们将会在此路径读到myfile.txt文件,看到打印出来的数据,如下gif:

  • fgets操作(每次读文件的一行))

fgets函数从给定stream中读取size个字节放到str中,当发现换行符、文件结束符或发生错误时,读取将停止。

#include <stdio.h>
#include <string.h>

int main(){
    FILE* pf = fopen("myfile.txt","r");
    if(!pf){
        perror("fopen");
        return 1;
    }

    char buffer[1024];

    const char* msg = "hello xxxxx\n";    

    while(1){    
        char* r = fgets(buffer,sizeof(msg),pf);                                                  
        if(!r)    
            break;    
        printf("%s",buffer);    
    }

    fclose(pf);
    return 0;
}

  • 打开文件的方式:
			 r      打开文本文件,用于读。流被定位于文件的开始。

       r+     打开文本文件,用于读写。流被定位于文件的开始。

       w      将文件长度截断为零,或者创建文本文件,用于写。流被定位于文件的开始。

       w+     打开文件,用于读写。如果文件不存在就创建它,否则将截断它。流被定位于文件的开始。

       a      打开文件,用于追加
              (在文件尾写)。如果文件不存在就创建它。流被定位于文件的末尾。

       a+     打开文件,用于追加
              (在文件尾写)。如果文件不存在就创建它。读文件的初始位置是文件的开始,但是输出总是被追加到文件的末尾。

2、三个数据流stdin、stdout、stderr

  • stdin(标准输入流):我们在C语言常用的scanf函数,其默认就是指定了标准输入流stdin
#include <stdio.h>

int main(){
  int x = 0;
  scanf("%d",&x);
  return 0;
}

fread使用标准输入流:

#include <stdio.h>    
#include <string.h>    
    
int main(){ 
		char buffer[10];    
    // 这里要注意,这里的sizeof(buffer)-1是多少,fread就会去读多少数据,直到读到这么多数据    
    size_t bytesRead = fread(buffer, sizeof(char), sizeof(buffer)-1, stdin);    
    buffer[bytesRead] = '\0';  // 字符串结束符    
    printf("buff:%s",buffer);
    return 0;
}

如果我们想指定输入流,我们可以使用fscanf函数:

#include <stdio.h>    
    
int main(){    
    
    FILE* pf = fopen("myfile","r");    
    if(!pf){    
        perror("fopen error");    
        return 1;    
    }    
    
    int in = 0;    
    fscanf(pf,"%d",&in);    
    printf("%d\n",in);
  	fclose(pf);
    
    return 0;    
}

  • stdout(标准输出流):我们在C语言常用的printf函数,其默认就是指定了标准输入流stdout
#include <stdio.h>

int main(){
  printf("%d\n",2);
  return 0;
}

fwrite使用标准输出流:

#include <stdio.h>    
#include <string.h>                                                                              
    
int main(){    
    
    FILE* pf = fopen("myfile","w");    
    if(!pf){    
        perror("fopen");    
        return 1;    
    }    
    
    const char* msg = "hello fprintf\n";       
    
    fwrite(msg,strlen(msg),1,stdout);
  	fclose(pf);	
    return 0;    
}

如果我们想指定输出流,我们可以使用fprintf函数:

#include <stdio.h>    
    
int main(){    
    
    FILE* pf = fopen("myfile","w");    
    if(!pf){    
        perror("fopen error");    
        return 1;    
    }    
  
    const char* msg = "hello fprintf\n";    
    fprintf(pf,"%s",msg);                                                                       
    fclose(pf);
    return 0;    
}

  • stderr(标准输入流)(后面讲)

3、系统文件IO

3.1、相关系统调用接口的使用

  • 类比C标准库中的函数fopen、fwrite、fread等,我们称之为库函数(权限低)(底层就是使用系统调用接口)。

  • 而open、write、read等属于系统提供的接口,我们称之为系统调用接口(权限高)。

  • 参考下图的lib和系统调用接口

  • open打开文件,close关闭文件

这里注意有两个open系统调用接口,第一个不带第三个参数mode,这个mode就是给创建的文件设置权限的比如0666,就是让这个文件的root、自己、其他人的权限都设置为只能读写,当然,这个还要看权限掩码umask是多少,假如umask是0002这最后文件的权限为0664(前面的博客有讲权限掩码,自行查阅)。

  • 系统调用接口write写文件

write接口试图从buf中取nbyte字节的数据写到到文件描述符fildes指向的空间中

#include <stdio.h>    
#include <fcntl.h>    
#include <unistd.h>    
#include <string.h>    
    
int main(){    
        
    int fd = open("myfile",O_WRONLY|O_TRUNC|O_CREAT,0666);                      
    		 printf("%d\n",fd);// 3
  	 const char* msg = "hello write\n";    
    write(fd,msg,strlen(msg));    
    
    close(fd);    
    return 0;    
}
  • 系统调用接口read读文件

read接口试图从文件描述符fildes指向的空间中读取nbyte字节放到buf

#include <stdio.h>    
#include <fcntl.h>    
#include <unistd.h>    
#include <string.h>    
    
int main(){    
        
    int fd = open("myfile",O_RDONLY);    
        		 printf("%d\n",fd);// 3
    char buff[64];        
    const char* msg = "hello write\n";    
        
    read(fd,buff,strlen(msg));    
    printf("%s",buff);
    
    close(fd);    
    return 0;    
}

3.2、文件描述符fd

  • 经过上面对文件myfile的写读,我们知道这个open的返回值fd是3,那么为什么返回值是3?我们再打开几个文件试试它们的文件描述符到底是多少:

    #include <stdio.h>    
    #include <fcntl.h>    
    #include <unistd.h>    
    #include <string.h>    
        
    int main(){
    		int fd1 = open("myfile1",O_WRONLY|O_TRUNC|O_CREAT,0666);    
        int fd2 = open("myfile2",O_WRONLY|O_TRUNC|O_CREAT,0666);    
        int fd3 = open("myfile3",O_WRONLY|O_TRUNC|O_CREAT,0666);    
        int fd4 = open("myfile4",O_WRONLY|O_TRUNC|O_CREAT,0666);    
        int fd5 = open("myfile5",O_WRONLY|O_TRUNC|O_CREAT,0666);    
        
        printf("%d\n",fd1);    
        printf("%d\n",fd2);    
        printf("%d\n",fd3);                                                         
        printf("%d\n",fd4);    
        printf("%d\n",fd5);
     
      	//close省时间不写了
      	return 0;
    }
    

    我们得出结果是:

    这像是什么?是不是数组的下标!那么问题又来了,前面的0,1,2去哪了?这时我们想到还有三个数据流,分别是stdin、stdout、stderr。我们继续来验证一下:

    #include <stdio.h>    
    #include <fcntl.h>    
    #include <unistd.h>    
    #include <string.h>    
        
    int main(){
    		int fd1 = open("myfile1",O_WRONLY|O_TRUNC|O_CREAT,0666);    
        int fd2 = open("myfile2",O_WRONLY|O_TRUNC|O_CREAT,0666);    
        int fd3 = open("myfile3",O_WRONLY|O_TRUNC|O_CREAT,0666);    
        int fd4 = open("myfile4",O_WRONLY|O_TRUNC|O_CREAT,0666);    
        int fd5 = open("myfile5",O_WRONLY|O_TRUNC|O_CREAT,0666);    
        
        printf("%d\n",stdin->_fileno);    
        printf("%d\n",stdout->_fileno);    
        printf("%d\n",stderr->_fileno);    
        printf("%d\n",fd1);    
        printf("%d\n",fd2);                                                         
        printf("%d\n",fd3);    
        printf("%d\n",fd4);    
        printf("%d\n",fd5);
     
      	//close省时间不写了
      	return 0;
    }
    

    这个打印出来的结果是:

    因此验证了我们的猜想!

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


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

  • 来看下列代码:
#include <stdio.h>    
#include <fcntl.h>    
#include <unistd.h>    
                                                                                
int main(){    
    
    int fd = open("myfile",O_RDONLY);    
    
    if(fd < 0){    
        perror("open");    
        return 1;    
    }    
    printf("%d\n",fd);    
    
    close(fd);    
    return 0;    
}

输出是3,关闭0或2试试(这里不能关闭1,因为1是标准输出流,关闭了我们就看不到输出结果,想看到输出结果我们需要进行输出重定向,下面会讲)

#include <stdio.h>    
#include <fcntl.h>    
#include <unistd.h>    
                                                                                
int main(){    
    
  	close(0);
  	//close(2);
    int fd = open("myfile",O_RDONLY);    
    
    if(fd < 0){    
        perror("open");    
        return 1;    
    }    
    printf("%d\n",fd);    
    
    close(fd);    
    return 0;    
}

输出是0或者2。说明了什么?说明文件描述符的分配规则是在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。


3.3、重定向

  • 重定向分为输入重定向和输出重定向

  • 针对上面关闭1的情况,关闭1后看不到输出结果了,那么我们可以进行输出重定向,看下面代码:

    #include <stdio.h>    
    #include <fcntl.h>    
    #include <unistd.h>    
                                                                                    
    int main(){    
        
      	close(1);
      	
        int fd = open("myfile",O_WRONLY|O_CREAT,0666);    
        
        if(fd < 0){    
            perror("open");    
            return 1;    
        }    
        printf("%d\n",fd);    
        
      	fflush(stdout);// 	刷新缓冲区
        close(fd);    
        return 0;    
    }
    

    我们发现,本来应该输出到显示器上的数据,输出到了myfile文件中!这种现象叫做输出重定向。输入重定向类似(从myfile文件中读取数据)。

    常见的重定向有:>, >>, <

  • 那么重定向的本质是什么?看下图:

    也就是将file*的指针重新指向新的文件。

  • 除了使用关闭标准输入输出流的方法实现重定向功能,还可以使用dup2系统调用。

    dup2系统调用,fildes2文件描述符是指定的,fildes是需要重定向的文件描述符。

    使用dup2系统调用代码:

    #include <stdio.h>    
    #include <fcntl.h>    
    #include <unistd.h>    
       
    int main(){    
       
       int fd = open("myfile",O_RDWR|O_CREAT,0666);                                   
       if(fd < 0){    
           perror("open");    
           return 1;    
       }    
       
       close(1);    
       dup2(fd,1);    
       
       printf("hello dup2\n");    
       fflush(stdout);    
       return 0;    
    }
    

    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    
    int main() {
       int fd = open("./log", O_CREAT | O_RDWR);
       if (fd < 0) {
           perror("open");
           return 1;
       }
       close(1);
       dup2(fd, 1);
       for (;;) {
           char buf[1024] = {0};
           ssize_t read_size = read(0, buf, sizeof(buf) - 1);// 从标准输入流(命令行)读取数据
           if (read_size < 0) {
               perror("read");
               break;
           }
           printf("%s", buf);
           fflush(stdout);
       }
       return 0;
    }
    

3.4、自制shell加入重定向功能

  • myshell.c文件

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <fcntl.h>
    #include <sys/stat.h>
                                        
    #define SIZE 1024
    #define MAX_LEN 128
    #define SEP " "
                                        
    // 下面的都和重定向有关
    #define NoneRedir  -1
    #define StdinRedir  0
    #define StdoutRedir 1
    #define AppendRedir 2
                                        
    //#define IgnSpace(buf,pos) do{ while(isspace(buf[pos])) pos++; }while(0)
                                        
    int redir_type = NoneRedir;
    char *filename = NULL;
                                        
    char* argv[MAX_LEN];//命令行字符串数组
    char pwd[SIZE];
    char envtemp[SIZE];
    int lastcode = 0;//退出码
                                        
    const char* HostName(){
        char* hostname = getenv("HOSTNAME");
        if(hostname) return hostname;
        else return "None";
    }
                                        
    const char* UserName(){
        char* hostname = getenv("USER");
        if(hostname) return hostname;
        else return "None";
    }
                                        
    const char* CurrentWorkDir(){
        char* hostname = getenv("PWD");
        if(hostname) return hostname;
        else return "None";
    }
                                        
    char* Home(){
        char* hostname = getenv("HOME");
        if(hostname) return hostname;
        else return "None"; 
    }
                                        
    int Interactive(char* commandline, int size){
        printf("[%s@%s %s]$ ",UserName(),HostName(),CurrentWorkDir());
        fgets(commandline,SIZE,stdin);
        commandline[strlen(commandline)-1] = '\0';
                                        
        return strlen(commandline);//空串返回0
    }
                                        
    void Check_redir(char in[])
    {
         // ls -a -l
        // ls -a -l > log.txt
        // ls -a -l >> log.txt
        // cat < log.txt
        redir_type = NoneRedir;
        filename = NULL;
        int pos = strlen(in) - 1;
        while( pos >= 0 )
        {
            if(in[pos] == '>')
            {
                if(in[pos-1] == '>')
                {
                    redir_type = AppendRedir;
                    in[pos-1] = 0;
                    pos++;
                    //IgnSpace(in, pos);
                                        
                    while(in[pos] == ' '){
                        pos++;
                                                    }
                                        
                    filename = in+pos;
                    break;
                                }
                else
                {
                    redir_type = StdoutRedir;
                    in[pos++] = 0;
                    //IgnSpace(in, pos);
                    while(in[pos] == ' '){
                        pos++;
                    }
                    filename = in+pos;
                    //printf("debug: %s, %d\n", filename, redir_type);
                    break;
                }
            }
            else if(in[pos] == '<')
            {
                redir_type = StdinRedir;
                in[pos++] = 0;
                //IgnSpace(in, pos);
                while(in[pos] == ' '){
                    pos++;
                }
                                        
                filename = in+pos;
                //printf("debug: %s, %d\n", filename, redir_type);
                                            break;
            }
            else
            {
                pos--;
            }
        }
    }
                                        
    void Split(char* commandline){
                                        
        Check_redir(commandline);
                                        
        int i = 0;
        argv[i++] = strtok(commandline,SEP);
        while(argv[i++] = strtok(NULL,SEP));
        //解决ls无彩色问题
        if(strcmp(argv[0],"ls") == 0){
            argv[i-1] = (char*)"--color";
            argv[i] = NULL;
        }
    }
                                        
    int BuildingCmd(){
        int ret = 0;
        if(strcmp(argv[0],"cd") == 0){
            ret = 1;
            char* target = argv[1];
            //cd XXX 和cd
            if(!target) target = Home();//第二个参数为NULL
            //改变当前工作目录
            chdir(target);
            //处理target为..的情况
            //重新获取当前路径
            char temp[1024];
            getcwd(temp,1024);
            //更新当前环境变量PWD
            snprintf(pwd,SIZE,"PWD=%s",temp);
            //导出环境变量
            putenv(pwd);
        }
        else if(strcmp(argv[0],"export") == 0){
            ret = 1;
            if(argv[1]){
                strcpy(envtemp,argv[1]);
                putenv(envtemp);
            }
        }
        else if(strcmp(argv[0],"echo") == 0){
            ret = 1;
            if(argv[1] == NULL){
                printf("\n");
            }else{
                if(argv[1][0] == '$'){
                    if(argv[1][1] == '?'){
                        printf("%d\n",lastcode);
                        lastcode = 0;
                    }else{
                        char* e = getenv(argv[1]+1);
                        if(e) printf("%s\n",e);
                    }
                }
                else{
                    printf("%s\n",argv[1]);
                }
            }
        }
        return ret;
    }
                                        
    void Execute(){
        //只能交给子进程,如果用父进程执行命令行,执行一次就结束了
        pid_t id = fork();
        if(id < 0) perror("fork\n");
        else if(id == 0){
        int fd = -1;
        if(redir_type == StdinRedir)
        {
            fd = open(filename, O_RDONLY);
            dup2(fd, 0);
        }
        else if(redir_type == StdoutRedir)
        {
            umask(0);
            fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC,0666);
            dup2(fd, 1);
        }
        else if(redir_type == AppendRedir)
        {
            fd = open(filename, O_CREAT | O_WRONLY | O_APPEND);
            dup2(fd, 1);
        }
        else
        {
            // do nothing
        }
                                        
            execvp(argv[0],argv);
            exit(1);//执行完退出
        }
        //父进程等待子进程
        int status = 0;
        pid_t rid = waitpid(id,&status,0);
        if(rid == id) lastcode = WEXITSTATUS(status);//等待成功
    }
                                        
    int main(){
                                        
        while(1){
            char commandline[SIZE];
            //1. 打印命令行提示符,获取用户的命令字符串
            int n = Interactive(commandline,SIZE);
            if(!n) continue;//返回值为0就是空串,下面代码不再执行
            //2. 对命令行字符串进行切割
            Split(commandline);
            //3. 处理内建命令
            n = BuildingCmd();
            if(n) continue;
            //4. 执行这个命令
            Execute();
        }
        //int i;
        //for(i=0;argv[i];++i){
        //    printf("argv[%d]:%s\n",i,argv[i]);
        //}
        return 0;
    }
    

4、FILE

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

  • 研究以下代码看输出结果

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

int main(){    

   //int fd  = open("myfile",O_RDWR|O_CREAT|O_TRUNC,0666);    

   const char* msg1 = "hello printf\n"; // \n自带刷新    
   const char* msg2 = "hello fwrite\n";    
   const char* msg3 = "hello write\n";    

   write(1,msg3,strlen(msg3));    
   printf("%s",msg1);    
   fwrite(msg2,strlen(msg2),1,stdout);    

   fork();    
   return 0;    
}

结果如下:

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

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

  • 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲

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

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

  • 图示:

  • 那么为什么会有缓冲区这个概念呢?
  • 可以提高使用者的效率
  • **减少系统调用次数:**调用系统调用是有时间和空间上的消耗的,增加缓冲区可以在数据填满过后再调用系统调用,减少调用系统调用的次数
  • **减少磁盘访问次数:**直接将数据写入磁盘会导致频繁的磁盘访问,而磁盘访问是一种相对较慢的操作。通过使用缓冲区,可以将多个写入操作合并到一起,减少磁盘访问次数,提高写入性能。
  • 模拟实现FILE结构体
  • mystdio.h文件

    #include<stdio.h>                                                                                                                                                     
                
    #define NONE_FLUSH (1<<1)
    #define LINE_FLUSH (1<<2)
    #define FULL_FLUSH (1<<3)
    #define SIZE 1024
                
    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);
                
    int my_fwrite(const char *ptr, int size,myFILE *stream);
                
    //int my_fread(void *ptr, int size, myFILE *stream);
                
    void my_fclose(myFILE* stream);
                
    const char *toString(int flag);
                
    void DebugPrint(myFILE *fp);
    
  • mystdio.c文件

    #include "mystdio.h"                                                                                                                                                                                                                                                                                                                                                                   
    #include <fcntl.h>           
    #include <string.h>          
    #include <sys/types.h>       
    #include <sys/stat.h>        
    #include <stdlib.h>      
    #include <unistd.h>      
                      
    myFILE* my_fopen(const char *path, const char *mode){      
        int flag = 0;      
        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;      
        }      
                      
        int fd = 0;      
        if(flag & O_WRONLY){      
            umask(0);      
            fd = open(path,flag,0666);      
        }else{      
            fd = open(path,flag);      
        }      
                      
        if(fd < 0) return NULL;      
                      
        myFILE* fp = (myFILE*)malloc(sizeof(myFILE));      
        if(!fp){      
            perror("malloc");      
            return NULL;      
        }      
                      
        fp->fileno = fd;      
        fp->pos = 0;      
        fp->cap = SIZE;      
        fp->flush_mode = LINE_FLUSH;      
                      
        return fp;      
    }      
                      
    void my_fflush(myFILE* stream){      
        if(stream->pos == 0) return;      
        write(stream->fileno,stream->buff,stream->pos);      
        stream->pos = 0;// 刷新后pos到最初位置      
    }      
                      
    int my_fwrite(const char *ptr, int size,myFILE *stream){      
        memcpy(stream->buff+stream->pos,ptr,size);// buff从pos开始       
        stream->pos += size;      
        if((stream->flush_mode & LINE_FLUSH) && (stream->buff[stream->pos-1] == '\n')){      
            my_fflush(stream);      
        }else if((stream->flush_mode & FULL_FLUSH) && (stream->pos == stream->cap)){      
            my_fflush(stream);      
        }      
                      
        return size;      
    }      
                      
    void my_fclose(myFILE* stream){      
        my_fflush(stream);      
        close(stream->fileno);      
        free(stream);      
    }      
                      
    const char *toString(int flag)      
    {      
        if(flag & NONE_FLUSH) return "None";      
        else if(flag & LINE_FLUSH) return "Line";      
        else if(flag & FULL_FLUSH) return "FULL";      
        return "Unknow";      
    }      
                      
    void DebugPrint(myFILE *fp)      
    {      
        printf("outbufer: %s\n", fp->buff);      
        printf("fd: %d\n", fp->fileno);      
        printf("pos: %d\n", fp->pos);      
        printf("cap: %d\n", fp->cap);      
        printf("flush_mode: %s\n", toString(fp->flush_mode));      
    } 
    
  • main.c文件

    #include "mystdio.h"    
    #include <string.h>    
    #include <unistd.h>    
                    
    int main(){    
        myFILE* fp = my_fopen("myfile","w");    
                    
        if(!fp) return 1;    
                    
        const char* msg = "hello my_fwrite\n";    
                    
        my_fwrite(msg,strlen(msg),fp);                                                                                                                                    
                    
        //int cnt = 5;    
        //char buffer[64];    
        //while(cnt)    
        //{    
        //    snprintf(buffer, sizeof(buffer), "helloworld,hellobit,%d  ", cnt--);    
        //    my_fwrite( buffer, strlen(buffer),fp);    
        //    DebugPrint(fp);    
        //    sleep(2);    
        //    //my_fflush(fp);    
        //}    
        //    
        //fork();    
                    
        my_fclose(fp);    
        return 0;    
    }
    

5、文件系统

我们在命令行输入ls -l ,除了看见文件名,还看见了一些文件的其他属性

其中还包含:

  • 模式
  • 硬链接数
  • 文件所有者
  • 文件所属组
  • 大小
  • 最后修改时间
  • 文件名

还可以通过stat命令来获取文件的更多信息

上面的执行结果有几个信息我们可以通过inode来解释一下


5.1、inode

为了能解释清楚inode,我们需要来理解一下文件系统,我们先看以下这张磁盘文件系统图:

磁盘是一个典型的块设备,磁盘分区被划分为一个个的block(块)。一个block的大小是在格式化的时候确定的,后面不可更改。

解释一下上述图片各名词的含义:

  • 块组(Block Group):文件系统会根据分区的大小划分为数个块组,一个块组就是一个分区,并且每个块组都会有相同的结构组成。

  • 超级块(Super Block):存放文件系统本身的信息。记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了,不过一般磁盘不止一个扇区存放有超级块,可能会有三四个扇区存放有超级块,以便于第一个扇区的超级块损坏可以通过其他扇区的超级块来恢复。

  • 块组描述(Group Discriptor Table):描述块组属性信息,也就是管理磁盘块位图、inode为徒、inode表、数据块的使用情况。

  • 块位图(Block Bitmap):块位图中记录着数据块中哪个数据块已经被占用,哪个数据块没有被占用。

  • inode位图(inode Bitmap):每个bite表示该inode编号是否已经被使用,比如0表示没有被使用,1表示已经被使用。

  • inode表(inode Table):存放文件属性。如文件大小,所有者,最近修改时间,使用的块情况等

  • 数据块(Data Block):存放文件内容。

将属性和数据分开存放的想法看起来很简单,但实际上是如何工作的呢?我们通过touch一个新文件来看看如何工作的。

查看inode编号ls -li

从整体上来看,创建一个文件所经历的步骤如下:

为了说明问题,我们将上图简化:

创建文件一般需要4个操作:

  1. 存储属性

    内核先找到一个空闲的i节点(这里是263466)。内核把文件信息记录到其中。

  2. 存储数据

    该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推。

  3. 记录分配情况

    文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。

  4. 添加文件名到目录

    新的文件名abc。linux如何在当前的目录中记录这个文件?内核将入口(263466,abc)(映射关系)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。


插写一个文件系统操作:

使用dd命令创建大文件并将其写入文件系统,然后将文件系统挂载到系统中,可以按照以下步骤进行:

  1. 创建目录 /path/to

    mkdir -p /path/to
    
  2. 使用 dd 命令创建大文件

    以下命令创建一个大小为1GB的空文件:

    dd if=/dev/zero of=/path/to/largefile bs=1M count=1024
    

    这将在指定路径创建名为 largefile 的大小为1GB的空文件。

  3. 使用 mkfs 创建文件系统

    在上面创建的文件中,可以使用适当的文件系统类型(例如ext4)来创建文件系统。假设要创建ext4文件系统:

    mkfs.ext4 /path/to/largefile
    

    这将在 /path/to/largefile 中创建一个ext4文件系统。

  4. 挂载文件系统

    创建文件系统后,您可以将其挂载到系统中的某个目录。首先,创建一个用于挂载的目录:

    mkdir /mnt/largefile
    

    然后将文件系统挂载到此目录:

    mount -o loop /path/to/largefile /mnt/largefile
    

    这将将 /path/to/largefile 中的文件系统挂载到 /mnt/largefile 目录中。


5.2、硬链接(Hard Link)

  • 硬链接是指在文件系统中,多个文件可以指向同一个inode,这样多个文件实际上指向了相同的数据块
  • 硬链接是文件系统级别的链接,它们只是指向了相同的inode,不保留路径信息,因此即使原文件移动或重命名,硬链接仍然有效。
  • 删除原文件并不会影响硬链接的可用性,只有当所有链接都被删除后,文件的inode和数据块才会被释放

使用硬链接:ln 原文件名 硬链接文件名,如下

ln file.txt file.hard

我们前面有讲到,文件的一行属性信息,有一个是硬链接数,我们能看到,一开始没给file.txt建立硬链接的时候,它的硬链接数1,就是只有自己,当给它建立硬链接后,硬链接的文件和原文件的硬链接数都变为2。

因此我们得出结论:普通文件的硬链接数等于该文件的建立的硬链接文件个数+1(可以自行再给file.txt文件再建立一个硬链接文件,看是不是硬链接数都变为3)。

我们再看目录的硬链接数:

我们看到,新创建的目录就有2个硬链接数!为什么是两个?我们看图可以知道,一个是自己的目录文件的,还有一个是该目录下的.目录的,我们知道目录.就是指当前目录。我们再来测试,在该目录下再创建5个目录,再看看硬链接数是多少?

能看到硬链接数变为7了!为什么呢?我们发现其中2个是自己和.目录,还有5个是该目录下的目录(a、b、c、d、e)下的..目录,显而易见..目录就是上级目录。因此我们得出结论:目录文件的硬链接数等于2+该目录下的目录个数。

注意:用户不能自己给目录建立硬链接,只能由系统创建!因为自己创建目录的硬链接会导致路径回路。

假如当前目录是/user/home,你给user目录创建一个硬链接/,哪当前创建的硬链接目录变为了/user/home/user,这就导致了路径回路。


5.3、软链接(Symbolic Link)

软链接也叫符号链接。

  • 软链接是一个特殊的文件,它包含了指向另一个文件的路径,相当于一个快捷方式
  • 软链接可以跨越文件系统,因为它们保存的是路径信息而不是inode号。
  • 软链接不像硬链接那样指向相同的数据块,而是指向目标文件的路径,因此即使原文件移动或重命名,软链接也可能失效。
  • 删除原文件后,软链接将成为死链接(即指向一个不存在的目标),如果不再需要,可以手动删除

使用软链接:ln -s 原文件 软链接文件。如:

ln -s file.txt file.symb


6、动态库和静态库

下面讲解动静态库可能会用到的文件

  • mystdio.h文件

    #include<stdio.h>
        
    #define NONE_FLUSH (1<<1)
    #define LINE_FLUSH (1<<2)
    #define FULL_FLUSH (1<<3)
    #define SIZE 1024
        
    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);
        
    int my_fwrite(const char *ptr, int size,myFILE *stream);
        
    //int my_fread(void *ptr, int size, myFILE *stream);
        
    void my_fclose(myFILE* stream);
        
    const char *toString(int flag);
        
    void DebugPrint(myFILE *fp);
    
  • mystdio.c文件

    #include "mystdio.h"
    #include <fcntl.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdlib.h>
    #include <unistd.h>
        
    myFILE* my_fopen(const char *path, const char *mode){
        int flag = 0;
        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;
        }
        
        int fd = 0;
        if(flag & O_WRONLY){
            umask(0);
            fd = open(path,flag,0666);
        }else{
            fd = open(path,flag);
        }
        
        if(fd < 0) return NULL;
        
        myFILE* fp = (myFILE*)malloc(sizeof(myFILE));
        if(!fp){
            perror("malloc");
            return NULL;
        }
        
        fp->fileno = fd;
        fp->pos = 0;
        fp->cap = SIZE;
        fp->flush_mode = LINE_FLUSH;
        
        return fp;
    }
        
    void my_fflush(myFILE* stream){
        if(stream->pos == 0) return;
        write(stream->fileno,stream->buff,stream->pos);
        stream->pos = 0;// 刷新后pos到最初位置
    }
        
    int my_fwrite(const char *ptr, int size,myFILE *stream){
        memcpy(stream->buff+stream->pos,ptr,size);// buff从pos开始 
        stream->pos += size;
        if((stream->flush_mode & LINE_FLUSH) && (stream->buff[stream->pos-1] == '\n')){
            my_fflush(stream);
        }else if((stream->flush_mode & FULL_FLUSH) && (stream->pos == stream->cap)){
            my_fflush(stream);
        }
        
        return size;
    }
        
    void my_fclose(myFILE* stream){
        my_fflush(stream);
        close(stream->fileno);
        free(stream);
    }
        
    const char *toString(int flag)
    {
        if(flag & NONE_FLUSH) return "None";
        else if(flag & LINE_FLUSH) return "Line";
        else if(flag & FULL_FLUSH) return "FULL";
        return "Unknow";
    }
        
    void DebugPrint(myFILE *fp)
    {
        printf("outbufer: %s\n", fp->buff);
        printf("fd: %d\n", fp->fileno);
        printf("pos: %d\n", fp->pos);
        printf("cap: %d\n", fp->cap);
        printf("flush_mode: %s\n", toString(fp->flush_mode));
    }
    
  • cal.h文件

    #pragma once                                                                       
            
    int Add(int a, int b);
    
  • cal.c文件

    #include "cal.h"    
            
    int Add(int a ,int b){    
        return a + b;                                                                  
    }
    
  • main.c文件

    #include <stdio.h>    
    #include "cal.h"    
    #include "mystdio.h"    
            
    int main(){    
        int res = Add(10,20);    
        printf("%d+%d=%d\n",10,20,res);    
            
        myFILE* fp = my_fopen("log1.txt","w");    
        if(fp) return 1;                                                               
        return 0;    
    }
    

6.1、静态库

静态库(.a文件):程序编译的时候把哭的代码链接到可执行文件中,程序运行时不再需要静态库

  • 生成静态库:将.o文件打包生成静态库

  • 使用归档工具:ar -rc 静态库名 .o文件

  • ar是gnu归档工具,rc表示(replace and create)

  • 这里的.o文件是用命令gcc -o 生成的(和动态库不一样,下面动态库会讲)

  • 需要注意的是:静态库名是有规范的,不能写成XXX.a,必须写成libXXX.a

  • 如:ar -rc libmylib.a *.o

  • 查看静态库列表ar -tv 静态库名

  • -t:列出静态库中的文件

  • -v:verbose 详细信息

  • 如:ar -tv libmylib.a

  • 使用静态库

  • 先将main.c生成main.o文件

    • 这里我们使用一个新命令 gcc -c .c文件,可以生成同名.o文件。如gcc -c main.c
  • 再使用命令:gcc - o 可执行文件 带main函数的目标文件(如main.o) -L 静态库路径 -l静态库名(去除前缀lib和后缀.a,如mylib) -static

  • gcc -o myexe main.c -L. -lmylib -static

  • 解释一下选项:

    • -L:指定静态库路径(当前路径可以使用.,也可以使用绝对路径)
    • -l:指定静态库
    • -static:指定是静态链接


6.2、动态库

动态库相对静态库的使用更复杂,原理也更复杂。

动态库(.so文件):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。

一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码(不像静态库那样直接在代码里面展开)。

在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)

动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。

  • 生成动态库:动态库的生成也需要目标文件,但是动态库的目标文件和静态库的目标文件的生成方式不一样,动态库目标文件必须使用选项fPIC(产生位置无关码)。

    • 先将.c文件生成.o文件:gcc -c -fPIC *.c,将.c文件生成同名.o文件

    • 再使用选项shared.o文件打包成动态库:gcc -shared -o 动态库名 .o文件

    • gcc -shared -o libmylib.so *.o

    • 需要注意的是:这个动态库名也有规则:不能是XXX.so,必须是libXXX.so

  • 使用动态库

    • 先用main.c文件生成main.o文件

    • 再使用命令gcc -o 可执行文件 带main函数的目标文件 -L动态库路径 -l(动态库名去掉前缀lib,去掉后缀.so)

    • gcc -o myexe main.o -L. -lmylib

  • 现在有一个问题:如果动态库不在当前目录下呢?

    我们来测试一下,将动态库和头文件放到一个目录下

    假设我们得到了一个压缩文件,文件解压后得到一个目录文件,目录文件里面包含所需所有的头文件和静态库

    这时候我们怎么运行这个main函数呢?

    这里我们又会使用到一个选项I,这个选项是用来指明头文件的路径

    现在出现一个新的问题!直接运行myexe会报错!

    报错的原因是找不到动态库libmyib.so。我们来看看该可执行文件所依赖的动态库,使用命令:ldd 可执行文件名。ldd即list dynamic dependence。

    ldd myexe

    也就是没找到该动态库。有下面四种解决办法:

    1、将该文件所需的动态库放到系统里的动态库中(不建议使用,风险高)。

    如:sudo cp mylib/lib/* /lib64

    2、使用环境变量

    使用命令:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库文件路径。这样导入环境变量的话,重启shell又需要重新配置。

    如:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/xp2/Linux_Primary/day_32/demo_02/dynamiclib/user/mylib/lib

    还可以去系统文件添加环境变量

    在这两个文件中:.bash_profile.bashrc文件。查找到这两个文件的方式就是先cd到用户路径cd ~,再ls -al查看这个路径下所有文件就可以看到这两个文件。

    3、使用软链接的方式(推荐,简单好用)

    在lib64目录下新建一个同动态库名的动态库文件的软链接。

    如:sudo ln -s /home/xp2/Linux_Primary/day_32/demo_02/dynamiclib/user/mylib/lib/libmylib.so /lib64/libmylib.so

    4、在系统目录下(/etc/ld.so.conf.d/)新建一个(文件名.conf)配置文件(推荐)


那么好,Linux基础IO就到这里,如果你对Linux和C++也感兴趣的话,可以看看我的主页哦。下面是我的github主页,里面记录了我的学习代码和leetcode的一些题的题解,有兴趣的可以看看。

Xpccccc的github主页

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

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

相关文章

【JavaEE】初识线程,线程与进程的区别

文章目录 ✍线程是什么&#xff1f;✍线程和进程的区别✍线程的创建1.继承 Thread 类2.实现Runnable接口3.匿名内部类4.匿名内部类创建 Runnable ⼦类对象5.lambda 表达式创建 Runnable ⼦类对象 ✍线程是什么&#xff1f; ⼀个线程就是⼀个 “执行流”. 每个线程之间都可以按…

Element-Plus下拉菜单边框去除教程

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

InputStream字节输入流和OutStream字节输出流

InputStream InputStream是Java标准库最基本的输入流&#xff0c;在java.io包内。它是一个抽象类。 FileInputStream&#xff1a;从文件中读取数据&#xff0c;是最终数据源。 int read()方法&#xff1a;读取输入流的下一个字节&#xff0c;并返回字节表示的int值&#xff08;…

什么是Prompt Tuning?

本文是观看视频What is Prompt Tuning?后的笔记。 大语言模型&#xff08;如ChatGPT &#xff09;是基础模型&#xff0c;是经过互联网上大量知识训练的大型可重用模型。 他们非常灵活&#xff0c;同样的模型可以分析法律文书或撰写文章。 但是&#xff0c;如果我们需要用其解…

C语言数据结构基础——排序

目录 1.插入排序 2.冒泡排序 3. 堆排序 4.希尔排序 5.直接选择排序 6.快速排序☆☆ 6.1快速排序基础 6.2关于快速排序的时间复杂度 6.3随机数法和三数取中法 6.4其他的单趟实现方法 6.4.1挖坑法 6.4.2前后指针版快速排序☆ 6.4.3非递归实现快排☆ 7.归并排序 7.1递归…

spark核心概念

DAG 所谓DAG就是有向无环图&#xff0c;其实就是个无环的流程&#xff0c;Spark的核心是根据RDD来实现的&#xff0c;Spark Scheduler!则为Spark核心实现的重要一环&#xff0c;其作用就是任务调度。Spark的任务调度就是如何组织任务去处理RDD中每个分区的数据&#xff0c;根据…

鱼眼相机的测距流程及误差分析[像素坐标系到空间一点以及测距和误差分析]

由于最近在整理单目测距的内容&#xff0c;顺手也总结下鱼眼相机的测距流程和误差分析&#xff0c;如果有错误&#xff0c;还请不吝赐教。 参考链接: 鱼眼镜头的成像原理到畸变矫正&#xff08;完整版&#xff09; 相机模型总结&#xff08;针孔、鱼眼、全景&#xff09; 三维…

医院设备管理平台:智能化管理,助推医院高效运行

在医疗科技飞速发展的今天&#xff0c;医院设备管理成为医院高效运营和优质服务的重要保障。信鸥科技凭借其深厚的技术积累和行业洞察力&#xff0c;推出了一款创新的医院设备管理平台&#xff0c;旨在帮助医院实现设备的智能化管理&#xff0c;提升运营效率&#xff0c;保障患…

WIFI驱动移植实验: openssl库的移植(wpa_supplicant 依赖库)

一. 简介 前面实现了WIFI驱动的移植&#xff0c;而连接某个WIFI热点上就需要用到 wpa_supplicant 工具&#xff0c;所以&#xff0c;本文开始为 移植 wpa_supplicant 工具做准备。 wpa_supplicant 依赖于 openssl库 与 libnl库&#xff0c;因此&#xff0c;需要移植一下open…

深入解析Spring MVC: 原理、流程【面试版】

什么是SpringMV? 1.是一个基于MVC的web框架&#xff1b; 2.是spring的一个模块&#xff0c;是spring的子容器&#xff0c;子容器可以拿父容器的东西&#xff0c;但是反过来不可&#xff1b; 2.SpringMVC的前端控制器是DispatcherServlet&#xff0c;用于分发请求。使开发变…

无需插件就能实现异构数据库的互联互通?(powershell妙用)

前两天在DBA群里有大佬分享了利用Oracle Database Gateway&#xff08;透明网关&#xff09;实现sqlserver和oracle 的数据交互&#xff0c;这里让我想到前些年写的一些powershell脚本用来做sqlserver和oracle的数据交互&#xff0c;powershell是windows自带的一个脚本工具&…

安防监控视频汇聚平台EasyCVR启用图形验证码之后如何调用login接口?

视频综合管理平台EasyCVR视频监控系统支持多协议接入、兼容多类型设备&#xff0c;平台可以将区域内所有部署的监控设备进行统一接入与集中汇聚管理&#xff0c;实现对监控区域的实时高清视频监控、录像与存储、设备管理、云台控制、语音对讲、级联共享等&#xff0c;在监控中心…

MongoDB Atlas维护指南:常见类型、注意事项与窗口设置

为了给Atlas用户更好的产品体验&#xff0c;MongoDB产品团队会进行定期维护。 本文将会介绍&#xff1a; 常见维护项目种类及频率&#xff0c;注意事项维护期间的影响及建议维护窗口设置说明维护告警设置和邮件通知范例 维护窗口常见项目 定期SSL证书轮换软件升级&#xff…

不要盲目开抖店,这才是开店的正确流程,2024全新版教程

我是王路飞。 抖音小店和视频号小店&#xff0c;我更建议没有经验的新手去做抖音小店。 虽然现在抖音小店不属于是一个蓝海项目了&#xff0c;但它依旧是我们普通人借助抖音流量变现非常重要的一个渠道&#xff0c;甚至没有之一。 至于视频号小店&#xff0c;可以说是当下最…

康耐视visionpro-CogPMAlignTool工具详细说明

CogPMAlignTool功能: 匹配产品形状,实现产品定位。 CogPMAlignTool操作说明: 1.打开工具栏,双击或点击鼠标拖拽添加CogPMAlignTool工具 ②.添加输入图像:右键“链接到”或以连线拖拽的方式选择相应输入图像 ③.点击“抓取训练图像”按钮获取训练图像 ④.在图像下拉列表中…

集体出走的Stability AI 发布全新代码大模型,3B以下性能最优,超越Code Llama和DeepSeek-Coder

Stability AI又有新动作&#xff01;程序员又有危机了&#xff1f; 3月26日&#xff0c;Stability AI推出了先进的代码语言模型Stable Code Instruct 3B&#xff0c;该模型是在Stable Code 3B的基础上进行指令调优的Code LM。 Stability AI 表示&#xff0c;Stable Code Instru…

frp 内网穿透

一、公网服务器运行frps -c my.ini my.ini文件代码 [common] bind_port 7000 #公网服务器开启的监听端口 二、在已经拿到shell的内网服务器1&#xff0c;即内网的web服务器运行 frpc -c frpc.ini frpc.ini文件代码 [common] server_addr 192.168.23.134 #公网服务…

vue 借助vue-amap插件对高德地图的简单使用

需求&#xff1a;实现点击获取经纬度、定位、对特殊位置标点及自定义信息窗体功能。 高德地图的官网API&#xff1a;概述-地图 JS API 1.4 | 高德地图API vue-amap的中文文档&#xff1a;组件 | vue-amap 实现&#xff1a; 1、安装vue-amap插件 npm install vue-amap --save…

linux之Haproxy

介绍 haproxy是一种开源的TCP和HTTP负载均衡代理服务器软件。客户端通过Haproxy代理服务器获得站点页面&#xff0c;而代理服务器收到客户请求后根据负载均衡的规则将请求数据转发给后端真实服务器 下载Haproxy yum install haproxy -y 开启服务 systemctl start haproxy 配…

Android笔记(三十):PorterDuffXfermode实现旋转进度View

背景 核心原理是使用PorterDuffXfermode Path来绘制进度&#xff0c;并实现圆角 效果图 Android笔记(三十)效果演示 进度条绘制步骤 将ImageView矩形七个点的坐标存储起来&#xff08;configNodes&#xff09; 他们对应着7个不同的刻度&#xff0c;每个刻度的值 i * &#…