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.mzph.cn/news/776834.shtml

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

相关文章

Docker 夺命连环 15 问

目录 什么是Docker&#xff1f; Docker的应用场景有哪些&#xff1f; Docker的优点有哪些&#xff1f; Docker与虚拟机的区别是什么&#xff1f; Docker的三大核心是什么&#xff1f; 如何快速安装Docker&#xff1f; 如何修改Docker的存储位置&#xff1f; Docker镜像常…

三位数组合-第12届蓝桥杯选拔赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第42讲。 三位数组合&#…

揭露非法集资陷阱!

常见的非法集资手法 犯罪分子利用了社会公众的哪些心理&#xff1f; 使用了怎样的措辞&#xff1f; 一起来揭露非法资金集聚的几个陷阱&#xff01; 拐弯抹角地向亲朋好友承诺大额回报&#xff0c;希望他们加入&#xff08;利用社会认同原则&#xff09;。 不法分子造了个传…

精灵传信系统 匿名性系统 支持网站+小程序双端源码

精灵传信支持在线提交发送短信&#xff0c;查看回复短信&#xff0c;在线购买额度&#xff0c;自定义对接易支付&#xff0c;设置违禁词&#xff0c;支持网站小程序双端。 项目 地 址 &#xff1a; runruncode.com/php/19720.html 环境要求: PHP > 73 MySQL>5.6 Ngi…

【算法-PID】

算法-PID ■ PID■ 闭环原理■ PID 控制流程■ PID 比例环节&#xff08; Proportion&#xff09;■ PID 积分环节&#xff08;Integral&#xff09;■ PID 微分环节&#xff08;Differential&#xff09; ■ PID PID 分别是 Proportion&#xff08;比例&#xff09;、 Integr…

RK3588平台开发系列讲解(硬件篇-最小系统设计)

目录 系统概述 概述 系统框图 应用框图 最小系统设计 时钟&#xff1a; 复位/看门狗/TSADC电路&#xff1a; 系统启动引导顺序&#xff1a; 系统概述 概述 RK3588 是一颗高性能、低功耗的应用处理器芯片&#xff0c;由4个Cortex-A76和4个Cortex-A55 及独立的 NEON协处理…

编程语言|C语言——C语言变量的存储方式

前言 变量是程序中数据的存储空间的抽象。变量的存储方式可分为静态存储和动态存储两种。 静态存储变量通常是在程序编译时就分配一定的存储空间并一直保持不变&#xff0c;直至整个程序结束。在上一部分中介绍的全局变量的存储方式即属于此类存储方式。 动态存储变量是在程序执…

C++自主点餐系统

一、 题目 设计一个自助点餐系统&#xff0c;方便顾客自己点餐&#xff0c;并提供对餐厅销售情况的统计和管理功能。 二、 业务流程图 三、 系统功能结构图 四、 类的设计 五、 程序代码与说明 头文件1. SystemMap.h #pragma once #ifndef SYSTEMMAP #define SYSTEMMAP #in…

集体出走的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…

3款免费甘特图制作工具的比较和选择指南

GanntProject GanttProject https://www.ganttproject.biz/ 是一款项目管理和调度应用&#xff0c;适用于 Windows、macOS 和 Linux。它易于使用&#xff0c;无需任何设置&#xff0c;适用于个人用户和小型团队。该应用提供任务层次结构和依存关系、里程碑、基准行、Gantt 图表…

WordPress Git主题 响应式CMS主题模板

分享的是新版本&#xff0c;旧版本少了很多功能&#xff0c;尤其在新版支持自动更新后&#xff0c;该主题可以用来搭建个人博客&#xff0c;素材下载网站&#xff0c;图片站等 主题特点 兼容 IE9、谷歌 Chrome 、火狐 Firefox 等主流浏览器 扁平化的设计加响应式布局&#x…

BioXcell InVivoPlus anti-mouse Ly6G及部分参考文献

BioXcell InVivoPlus anti-mouse Ly6G 1A8单克隆抗体与小鼠Ly6G反应。Ly6G分子量为21-25kDa&#xff0c;是GPI锚定的细胞表面蛋白Ly-6超家族的成员&#xff0c;在细胞信号传导和细胞粘附中发挥作用。Ly6G在发育过程中由骨髓谱系中的细胞&#xff08;包括单核细胞、巨噬细胞、粒…

android 集合总结

1 集合分类&#xff0c; collection和map两大类,Iterator接口是提供遍历任何Collection的接口&#xff0c;不是map 2 集合类的底层实现 hashset基于hashmap实现&#xff08;只不过HashSet里面的HashMap所有的value都是同一个Object而已&#xff09; treeset由红黑树实现 …

什么是根据人类反馈的强化学习Reinforcement Learning with Human Feedback(RLHF)?

基于人类反馈的强化学习&#xff08;Reinforcement learning with human feedback&#xff09;是近年来越来越受欢迎的一种前沿技术&#xff0c;用于提高大型语言模型的性能。这是种使用人类反馈训练这些模型的有效方法&#xff0c;而该方法的输入组件与搜索评估也有诸多相似之…

[Flutter]环境判断

方式一&#xff08;推荐&#xff09; 常量kReleaseMode&#xff0c;它会根据你的应用是以什么模式编译的来获取值。bool.fromEnvironment会从Dart编译时的环境变量中获取值。对于dart.vm.product这个特定的环境变量&#xff0c;它是由Dart VM设置的&#xff0c;用来标明当前是…

【Android 源码】Android源码下载指南

文章目录 前言安装Repo初始化Repo选择分支没有梯子替换为清华源 有梯子 下载源码下载开始参考 前言 这是关于Android源码下载的过程记录。 环境&#xff1a;Windows上通过VMware安装的Ubuntu系统 安装Repo 创建Repo文件目录 mkdir ~/bin PATH~/bin:$PATH下载Repo工具&#…

回文子串 每日温度 接雨水

647. 回文子串 力扣题目链接 如果s【i】和s【j】相同 dp【i1】【j-1】也是回文串的话 &#xff08;等于true&#xff09; 那么dp【i】【j】也是回文串 true 定义一个bool二维数组 遍历顺序是从下到上 从左到右 因为dp【i】【j】是通过dp【i1】【j-1】推出来的 i从最后一…

【漏洞复现】用友U8Cloud nc.bs.sm.login2.RegisterServlet SQL注入漏洞

0x01 产品简介 用友U8 Cloud是用友推出的新一代云ERP,主要聚焦成长型、创新型企业,提供企业级云ERP整体解决方案 0x02 漏洞概述 用友U8Cloud nc.bs.sm.login2.RegisterServlet接口处存在SQL注入漏洞,未授权的攻击者可通过此漏洞获取数据库权限,从而盗取用户数据,造成用…

OpenGL 实现“人像背景虚化“效果

手机上的人像模式,也被人们称作“背景虚化”或 ”双摄虚化“ 模式,也称为 Bokeh 模式,能够在保持画面中指定的人或物体清晰的同时,将其他的背景模糊掉。突出画面的主体部分,主观上美感更强烈。 人像模式的一般实现原理是,利用双摄系统获取景深信息,并通过深度传感器和图…

WorkPlus智能AI助理:定制化部署,拓展企业协作新境界

近年来&#xff0c;随着人工智能技术不断发展&#xff0c;智能助理在企业管理中发挥着日益重要的作用。其中&#xff0c;WorkPlus智能AI助理作为一款支持私有化部署的新一代智能助理软件&#xff0c;为企业内部协作场景带来了全新的可能性。通过结合企业自身的行业知识和应用场…