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、动态库

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; }
这里我们将会在此路径得到一个写有
5
行hello fopen
的myfile.txt
文件,如下gif:
fread操作(写文件)
fread
不会在字符串末尾添加 null 终止符(\0
),fgets
会。
fread函数从定
stream
里读取nitems
个size
字节大小的数据存到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个操作:
存储属性
内核先找到一个空闲的i节点(这里是263466)。内核把文件信息记录到其中。
存储数据
该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推。
记录分配情况
文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。
添加文件名到目录
新的文件名abc。linux如何在当前的目录中记录这个文件?内核将入口(263466,abc)(映射关系)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。
插写一个文件系统操作:
使用
dd
命令创建大文件并将其写入文件系统,然后将文件系统挂载到系统中,可以按照以下步骤进行:
创建目录
/path/to
:mkdir -p /path/to
使用 dd 命令创建大文件:
以下命令创建一个大小为1GB的空文件:
dd if=/dev/zero of=/path/to/largefile bs=1M count=1024
这将在指定路径创建名为
largefile
的大小为1GB的空文件。使用 mkfs 创建文件系统:
在上面创建的文件中,可以使用适当的文件系统类型(例如ext4)来创建文件系统。假设要创建ext4文件系统:
mkfs.ext4 /path/to/largefile
这将在
/path/to/largefile
中创建一个ext4文件系统。挂载文件系统:
创建文件系统后,您可以将其挂载到系统中的某个目录。首先,创建一个用于挂载的目录:
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主页
相关文章:

Linux文件(系统)IO(含动静态库的链接操作)
文章目录 Linux文件(系统)IO(含动静态库的链接操作)1、C语言文件IO操作2、三个数据流stdin、stdout、stderr3、系统文件IO3.1、相关系统调用接口的使用3.2、文件描述符fd3.3、文件描述符的分配规则3.3、重定向3.4、自制shell加入重…...

CI/CD实战-jenkins结合ansible 7
配置主机环境 在jenkins上断开并删除docker1节点 重新给master添加构建任务 将server3,server4作为测试主机,停掉其上后面的docker 在server2(jenkins)主机上安装ansible 设置jenkins用户到目标主机的免密 给测试主机创建用户并…...

内网渗透-(黄金票据和白银票据)详解(一)
目录 一、Kerberos协议 二、下面我们来具体分析Kerberos认证流程的每个步骤: 1、KRB_AS-REQ请求包分析 PA-ENC-TIMESTAMP PA_PAC_REQUEST 2、 KRB_AS_REP回复包分析: TGT认购权证 Logon Session Key ticket 3、然后继续来讲相关的TGS的认证过程…...

学习transformer模型-Dropout的简明介绍
Dropout的定义和目的: Dropout 是一种神经网络正则化技术,它在训练时以指定的概率丢弃一个单元(以及连接)p。 这个想法是为了防止神经网络变得过于依赖特定连接的共同适应,因为这可能是过度拟合的症状。直观上&#…...

游戏引擎中的大气和云的渲染
一、大气 首先和光线追踪类似,大气渲染也有类似的渲染公式,在实际处理中也有类似 Blinn-Phong的拟合模型。关键参数是当前点到天顶的角度和到太阳的角度 二、大气散射理论 光和介质的接触: Absorption 吸收Out-scattering 散射Emission …...
华为鲲鹏云认证考试内容有哪些?华为鲲鹏云认证考试报名条件
华为鲲鹏云认证考试是华为公司为了验证IT专业人士在鲲鹏计算及云计算领域的专业能力而设立的一项认证考试。以下是关于华为鲲鹏云认证考试的一些详细信息: 考试内容:华为鲲鹏云认证考试的内容主要包括理论考核和实践考核两大部分。理论考核涉及云计算、…...

v3-admin-vite 改造自动路由,view页面自解释Meta
需求 v3-admin-vite是一款不错的后端管理模板,主要是pany一直都在维护,最近将后台管理也进行了升级,顺便完成一直没时间解决的小痛痒: 在不使用后端动态管理的情况下。我不希望单独维护一份路由定义,我希望页面是自解…...
FIFO存储器选型参数,结构原理,工艺与注意问题总结
🏡《总目录》 目录 1,概述2.1,写入操作2.2,读取操作2.3,指针移动与循环2.4,状态检测3,结构特点3.1,双口RAM结构3.2,无外部读写地址线3.3,内部读写指针自动递增3.4,固定深度的缓冲区4,工艺流程4.1,硅晶圆准备...
jvm高级面试题-2024
说下对JVM内存模型的理解 JVM内存模型主要是指Java虚拟机在运行时所使用的内存结构。它主要包括堆、栈、方法区和程序计数器等部分。 堆是JVM中最大的一块内存区域,用于存储对象实例。一般通过new关键字创建的对象都存放在堆中,堆的大小可以通过启动参数…...

DeepL Pro3.1 下载地址及安装教程
DeepL Pro是DeepL公司推出的专业翻译服务。DeepL是一家专注于机器翻译和自然语言处理技术的公司,其翻译引擎被认为在质量和准确性方面表现优秀.DeepL Pro提供了一系列高级功能和服务,以满足专业用户的翻译需求。其中包括: 高质量翻译…...
第十一届 “MathorCup“- B题:基于机器学习的团簇能量预测及结构全局寻优方法
目录 摘 要 第 1 章 问题重述 1.1 问题背景 1.2 问题描述 第 2 章 思路分析...

云计算探索-如何在服务器上配置RAID(附模拟器)
一,引言 RAID(Redundant Array of Independent Disks)是一种将多个物理硬盘组合成一个逻辑单元的技术,旨在提升数据存取速度、增大存储容量以及提高数据可靠性。在服务器环境中配置RAID尤其重要,它不仅能够应对高并发访…...

LeetCode226:反转二叉树
题目描述 给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。 解题思想 使用前序遍历和后序遍历比较方便 代码 class Solution { public:TreeNode* invertTree(TreeNode* root) {if (root nullptr) return root;swap(root->left, root…...

特征融合篇 | 利用RT-DETR的AIFI去替换YOLOv8中的SPPF(附2种改进方法)
前言:Hello大家好,我是小哥谈。RT-DETR模型是一种用于目标检测的深度学习模型,它基于transformer架构,特别适用于实时处理序列数据。在RT-DETR模型中,AIFI(基于注意力的内部尺度特征交互)模块是一个关键组件,它通过引入注意力机制来增强模型对局部和全局信息的处理能力…...

MVCC多版本并发控制
1.什么是MVCC MVCC (Multiversion Concurrency Control),多版本并发控制。MySQL通过MVCC来实现隔离性。隔离性本质上是因为同时存在多个并发事务可能会导致脏读、幻读等情况。要解决并发问题只有一种方案就是加锁。当然,锁不可避免…...
图片转换成base64如何在html文件中使用呢
在HTML文件中使用Base64编码的图片非常简单。Base64编码是一种将二进制数据转换为ASCII字符串的方法,这使得可以直接在网页上嵌入图片数据,而无需引用外部图片文件。以下是如何在HTML中使用Base64编码的图片的步骤: 步骤 1: 将图片转换为Bas…...
【MATLAB源码-第24期】基于matlab的水声通信中海洋噪声的建模仿真,对比不同风速的影响。
操作环境: MATLAB 2022a 1、算法描述 水声通信: 水声通信是一种利用水中传播声波的方式进行信息传递的技术。它在水下环境中被广泛应用,特别是在海洋科学研究、海洋资源勘探、水下军事通信等领域。 1. **传输媒介**:水声通信利…...

七、函数的使用方法
函数的调用 nameinput()#输入参数并赋值name print(name)#d打印name 格式:返回值函数名(参数) def get_sum(n):#形式参数计算累加和:param n::return: sumsum0for i in range(1,n1):sumiprint…...

数据分析之Tebleau 简介、安装及数据导入
Tebleau简介 Tebleau基于斯坦福大学突破性交互式技术 可以将结构化数据快速生成图表、坐标图、仪表盘与报告 将维度拖放到画布等地方是他的主要操作方式 例:Tebleau是手机相机 (相对来说更简单) POWER BI是单反相机 Tebleau各类产品 Teblea…...
分享一下设计模式的学习
分享一下设计模式的学习 1、什么是设计模式? 设计模式是在软件设计过程中,经过实践和总结得出的描述、解决软件设计问题的一种经验总结。它是一种被广泛接受和验证的最佳实践,用于解决特定类型的问题,并提供了可重用的解决方案。…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)
引言 工欲善其事,必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后,我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集,就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...

AI语音助手的Python实现
引言 语音助手(如小爱同学、Siri)通过语音识别、自然语言处理(NLP)和语音合成技术,为用户提供直观、高效的交互体验。随着人工智能的普及,Python开发者可以利用开源库和AI模型,快速构建自定义语音助手。本文由浅入深,详细介绍如何使用Python开发AI语音助手,涵盖基础功…...

什么是VR全景技术
VR全景技术,全称为虚拟现实全景技术,是通过计算机图像模拟生成三维空间中的虚拟世界,使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验,结合图文、3D、音视频等多媒体元素…...

数学建模-滑翔伞伞翼面积的设计,运动状态计算和优化 !
我们考虑滑翔伞的伞翼面积设计问题以及运动状态描述。滑翔伞的性能主要取决于伞翼面积、气动特性以及飞行员的重量。我们的目标是建立数学模型来描述滑翔伞的运动状态,并优化伞翼面积的设计。 一、问题分析 滑翔伞在飞行过程中受到重力、升力和阻力的作用。升力和阻力与伞翼面…...
TCP/IP 网络编程 | 服务端 客户端的封装
设计模式 文章目录 设计模式一、socket.h 接口(interface)二、socket.cpp 实现(implementation)三、server.cpp 使用封装(main 函数)四、client.cpp 使用封装(main 函数)五、退出方法…...