做網(wǎng)站編程用什么語言好seo怎么做最佳
文章目錄
- Linux文件(系統(tǒng))IO(含動(dòng)靜態(tài)庫的鏈接操作)
- 1、C語言文件IO操作
- 2、三個(gè)數(shù)據(jù)流stdin、stdout、stderr
- 3、系統(tǒng)文件IO
- 3.1、相關(guān)系統(tǒng)調(diào)用接口的使用
- 3.2、文件描述符fd
- 3.3、文件描述符的分配規(guī)則
- 3.3、重定向
- 3.4、自制shell加入重定向功能
- 4、FILE
- 5、文件系統(tǒng)
- 5.1、inode
- 5.2、硬鏈接(Hard Link)
- 5.3、軟鏈接(Symbolic Link)
- 6、動(dòng)態(tài)庫和靜態(tài)庫
- 6.1、靜態(tài)庫
- 6.2、動(dòng)態(tài)庫

Linux文件(系統(tǒng))IO(含動(dòng)靜態(tài)庫的鏈接操作)
1、C語言文件IO操作
- fopen打開文件,fclose關(guān)閉文件
- fwrite操作(寫文件)
fwrite函數(shù)從
ptr
里將nitems
個(gè)大小為size
字節(jié)的數(shù)據(jù)寫進(jìn)定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; }
這里我們將會(huì)在此路徑得到一個(gè)寫有
5
行hello fopen
的myfile.txt
文件,如下gif:
fread操作(寫文件)
fread
不會(huì)在字符串末尾添加 null 終止符(\0
),fgets
會(huì)。
fread函數(shù)從定
stream
里讀取nitems
個(gè)size
字節(jié)大小的數(shù)據(jù)存到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個(gè)字節(jié),讀1024次(文件結(jié)束就不讀)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; }
這里我們將會(huì)在此路徑讀到
myfile.txt
文件,看到打印出來的數(shù)據(jù),如下gif:
fgets操作(每次讀文件的一行))
fgets函數(shù)從給定
stream
中讀取size
個(gè)字節(jié)放到str
中,當(dāng)發(fā)現(xiàn)換行符、文件結(jié)束符或發(fā)生錯(cuò)誤時(shí),讀取將停止。#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 將文件長度截?cái)酁榱?#xff0c;或者創(chuàng)建文本文件,用于寫。流被定位于文件的開始。w+ 打開文件,用于讀寫。如果文件不存在就創(chuàng)建它,否則將截?cái)嗨?。流被定位于文件的開始。a 打開文件,用于追加(在文件尾寫)。如果文件不存在就創(chuàng)建它。流被定位于文件的末尾。a+ 打開文件,用于追加(在文件尾寫)。如果文件不存在就創(chuàng)建它。讀文件的初始位置是文件的開始,但是輸出總是被追加到文件的末尾。
2、三個(gè)數(shù)據(jù)流stdin、stdout、stderr
- stdin(標(biāo)準(zhǔn)輸入流):我們?cè)贑語言常用的scanf函數(shù),其默認(rèn)就是指定了標(biāo)準(zhǔn)輸入流stdin
#include <stdio.h>int main(){int x = 0;scanf("%d",&x);return 0; }
fread使用標(biāo)準(zhǔn)輸入流:
#include <stdio.h> #include <string.h> int main(){ char buffer[10]; // 這里要注意,這里的sizeof(buffer)-1是多少,fread就會(huì)去讀多少數(shù)據(jù),直到讀到這么多數(shù)據(jù) size_t bytesRead = fread(buffer, sizeof(char), sizeof(buffer)-1, stdin); buffer[bytesRead] = '\0'; // 字符串結(jié)束符 printf("buff:%s",buffer);return 0; }
如果我們想指定輸入流,我們可以使用fscanf函數(shù):
#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(標(biāo)準(zhǔn)輸出流):我們?cè)贑語言常用的printf函數(shù),其默認(rèn)就是指定了標(biāo)準(zhǔn)輸入流stdout
#include <stdio.h>int main(){printf("%d\n",2);return 0; }
fwrite使用標(biāo)準(zhǔn)輸出流:
#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函數(shù):
#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(標(biāo)準(zhǔn)輸入流)(后面講)
3、系統(tǒng)文件IO
3.1、相關(guān)系統(tǒng)調(diào)用接口的使用
類比C標(biāo)準(zhǔn)庫中的函數(shù)fopen、fwrite、fread等,我們稱之為庫函數(shù)(權(quán)限低)(底層就是使用系統(tǒng)調(diào)用接口)。
而open、write、read等屬于系統(tǒng)提供的接口,我們稱之為系統(tǒng)調(diào)用接口(權(quán)限高)。
參考下圖的lib和系統(tǒng)調(diào)用接口
- open打開文件,close關(guān)閉文件
這里注意有兩個(gè)open系統(tǒng)調(diào)用接口,第一個(gè)不帶第三個(gè)參數(shù)
mode
,這個(gè)mode
就是給創(chuàng)建的文件設(shè)置權(quán)限的比如0666
,就是讓這個(gè)文件的root、自己、其他人
的權(quán)限都設(shè)置為只能讀寫,當(dāng)然,這個(gè)還要看權(quán)限掩碼umask是多少,假如umask是0002
這最后文件的權(quán)限為0664
(前面的博客有講權(quán)限掩碼,自行查閱)。
- 系統(tǒng)調(diào)用接口write寫文件
write接口試圖從
buf
中取nbyte
字節(jié)的數(shù)據(jù)寫到到文件描述符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; }
- 系統(tǒng)調(diào)用接口read讀文件
read接口試圖從文件描述符
fildes
指向的空間中讀取nbyte
字節(jié)放到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
經(jīng)過上面對(duì)文件myfile的寫讀,我們知道這個(gè)open的返回值fd是3,那么為什么返回值是3?我們?cè)俅蜷_幾個(gè)文件試試它們的文件描述符到底是多少:
#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省時(shí)間不寫了return 0; }
我們得出結(jié)果是:
這像是什么?是不是數(shù)組的下標(biāo)!那么問題又來了,前面的0,1,2去哪了?這時(shí)我們想到還有三個(gè)數(shù)據(jù)流,分別是stdin、stdout、stderr。我們繼續(xù)來驗(yàn)證一下:
#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省時(shí)間不寫了return 0; }
這個(gè)打印出來的結(jié)果是:
因此驗(yàn)證了我們的猜想!
而現(xiàn)在知道,文件描述符就是從0開始的小整數(shù)。當(dāng)我們打開文件時(shí),操作系統(tǒng)在內(nèi)存中要?jiǎng)?chuàng)建相應(yīng)的數(shù)據(jù)結(jié)構(gòu)來描述目標(biāo)文件。于是就有了file結(jié)構(gòu)體,表示一個(gè)已經(jīng)打開的文件對(duì)象。而進(jìn)程執(zhí)行open系統(tǒng)調(diào)用,所以必須讓進(jìn)程和文件關(guān)聯(lián)起來。每個(gè)進(jìn)程都有一個(gè)指針*files, 指向一張表files_struct,該表最重要的部分就是包涵一個(gè)指針數(shù)組,每個(gè)元素都是一個(gè)指向打開文件的指針!所以,本質(zhì)上,文件描述符就是該數(shù)組的下標(biāo)。所以,只要拿著文件描述符,就可以找到對(duì)應(yīng)的文件。如下圖:
3.3、文件描述符的分配規(guī)則
- 來看下列代碼:
#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,關(guān)閉0或2試試(這里不能關(guān)閉1,因?yàn)?是標(biāo)準(zhǔn)輸出流,關(guān)閉了我們就看不到輸出結(jié)果,想看到輸出結(jié)果我們需要進(jìn)行輸出重定向,下面會(huì)講)
#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。說明了什么?說明文件描述符的分配規(guī)則是在files_struct數(shù)組當(dāng)中,找到當(dāng)前沒有被使用的最小的一個(gè)下標(biāo),作為新的文件描述符。
3.3、重定向
重定向分為輸入重定向和輸出重定向
針對(duì)上面關(guān)閉1的情況,關(guān)閉1后看不到輸出結(jié)果了,那么我們可以進(jìn)行輸出重定向,看下面代碼:
#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);// 刷新緩沖區(qū)close(fd); return 0; }
我們發(fā)現(xiàn),本來應(yīng)該輸出到顯示器上的數(shù)據(jù),輸出到了myfile文件中!這種現(xiàn)象叫做輸出重定向。輸入重定向類似(從myfile文件中讀取數(shù)據(jù))。
常見的重定向有:
>, >>, <
那么重定向的本質(zhì)是什么?看下圖:
也就是將file*的指針重新指向新的文件。
除了使用關(guān)閉標(biāo)準(zhǔn)輸入輸出流的方法實(shí)現(xiàn)重定向功能,還可以使用dup2系統(tǒng)調(diào)用。
dup2系統(tǒng)調(diào)用,fildes2文件描述符是指定的,fildes是需要重定向的文件描述符。
使用dup2系統(tǒng)調(diào)用代碼:
#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);// 從標(biāo)準(zhǔn)輸入流(命令行)讀取數(shù)據(jù)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 " "// 下面的都和重定向有關(guān) #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];//命令行字符串?dāng)?shù)組 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();//第二個(gè)參數(shù)為NULL//改變當(dāng)前工作目錄chdir(target);//處理target為..的情況//重新獲取當(dāng)前路徑char temp[1024];getcwd(temp,1024);//更新當(dāng)前環(huán)境變量PWDsnprintf(pwd,SIZE,"PWD=%s",temp);//導(dǎo)出環(huán)境變量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(){//只能交給子進(jìn)程,如果用父進(jìn)程執(zhí)行命令行,執(zhí)行一次就結(jié)束了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);//執(zhí)行完退出}//父進(jìn)程等待子進(jìn)程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就是空串,下面代碼不再執(zhí)行//2. 對(duì)命令行字符串進(jìn)行切割Split(commandline);//3. 處理內(nèi)建命令n = BuildingCmd();if(n) continue;//4. 執(zhí)行這個(gè)命令Execute();}//int i;//for(i=0;argv[i];++i){// printf("argv[%d]:%s\n",i,argv[i]);//}return 0; }
4、FILE
因?yàn)镮O相關(guān)函數(shù)與系統(tǒng)調(diào)用接口對(duì)應(yīng),并且?guī)旌瘮?shù)封裝系統(tǒng)調(diào)用,所以本質(zhì)上,訪問文件都是通過fd訪問的。所以C庫當(dāng)中的FILE結(jié)構(gòu)體內(nèi)部,必定封裝了fd。
研究以下代碼看輸出結(jié)果
#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; }
結(jié)果如下:
但如果對(duì)進(jìn)程實(shí)現(xiàn)輸出重定向呢? ./buffer > buffile , 我們發(fā)現(xiàn)結(jié)果變成了:
我們發(fā)現(xiàn) printf 和 fwrite (庫函數(shù))都輸出了2次,而 write 只輸出了一次(系統(tǒng)調(diào)用)。為什么呢?肯定和fork有關(guān)!
一般C庫函數(shù)寫入文件時(shí)是全緩沖的,而寫入顯示器是行緩沖。
printf
和``fwrite `庫函數(shù)會(huì)自帶緩沖區(qū)(之前的進(jìn)度條例子就可以說明),當(dāng)發(fā)生重定向到普通文件時(shí),數(shù)據(jù)的緩沖方式由行緩沖變成了全緩沖。而我們放在緩沖區(qū)中的數(shù)據(jù),就不會(huì)被立即刷新,甚至fork之后。但是進(jìn)程退出之后,會(huì)統(tǒng)一刷新,寫入文件當(dāng)中。但是fork的時(shí)候,父子數(shù)據(jù)會(huì)發(fā)生寫時(shí)拷貝,所以當(dāng)你父進(jìn)程準(zhǔn)備刷新(刷新完相當(dāng)于清空數(shù)據(jù),就會(huì)發(fā)生寫時(shí)拷貝)的時(shí)候,子進(jìn)程也就有了同樣的一份數(shù)據(jù),隨即產(chǎn)生兩份數(shù)據(jù)。
write
沒有變化,說明沒有所謂的緩沖。圖示:
- 那么為什么會(huì)有緩沖區(qū)這個(gè)概念呢?
- 可以提高使用者的效率
- **減少系統(tǒng)調(diào)用次數(shù):**調(diào)用系統(tǒng)調(diào)用是有時(shí)間和空間上的消耗的,增加緩沖區(qū)可以在數(shù)據(jù)填滿過后再調(diào)用系統(tǒng)調(diào)用,減少調(diào)用系統(tǒng)調(diào)用的次數(shù)
- **減少磁盤訪問次數(shù):**直接將數(shù)據(jù)寫入磁盤會(huì)導(dǎo)致頻繁的磁盤訪問,而磁盤訪問是一種相對(duì)較慢的操作。通過使用緩沖區(qū),可以將多個(gè)寫入操作合并到一起,減少磁盤訪問次數(shù),提高寫入性能。
- 模擬實(shí)現(xiàn)FILE結(jié)構(gòu)體
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;// 當(dāng)前寫入位置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、文件系統(tǒng)
我們?cè)诿钚休斎?code>ls -l ,除了看見文件名,還看見了一些文件的其他屬性
其中還包含:
- 模式
- 硬鏈接數(shù)
- 文件所有者
- 文件所屬組
- 大小
- 最后修改時(shí)間
- 文件名
還可以通過stat命令來獲取文件的更多信息
上面的執(zhí)行結(jié)果有幾個(gè)信息我們可以通過inode來解釋一下
5.1、inode
為了能解釋清楚inode,我們需要來理解一下文件系統(tǒng),我們先看以下這張磁盤文件系統(tǒng)圖:
磁盤是一個(gè)典型的塊設(shè)備,磁盤分區(qū)被劃分為一個(gè)個(gè)的block(塊)。一個(gè)block的大小是在格式化的時(shí)候確定的,后面不可更改。
解釋一下上述圖片各名詞的含義:
塊組(Block Group):文件系統(tǒng)會(huì)根據(jù)分區(qū)的大小劃分為數(shù)個(gè)塊組,一個(gè)塊組就是一個(gè)分區(qū),并且每個(gè)塊組都會(huì)有相同的結(jié)構(gòu)組成。
超級(jí)塊(Super Block):存放文件系統(tǒng)本身的信息。記錄的信息主要有:bolck 和 inode的總量,未使用的block和inode的數(shù)量,一個(gè)block和inode的大小,最近一次掛載的時(shí)間,最近一次寫入數(shù)據(jù)的時(shí)間,最近一次檢驗(yàn)磁盤的時(shí)間等其他文件系統(tǒng)的相關(guān)信息。Super Block的信息被破壞,可以說整個(gè)文件系統(tǒng)結(jié)構(gòu)就被破壞了,不過一般磁盤不止一個(gè)扇區(qū)存放有超級(jí)塊,可能會(huì)有三四個(gè)扇區(qū)存放有超級(jí)塊,以便于第一個(gè)扇區(qū)的超級(jí)塊損壞可以通過其他扇區(qū)的超級(jí)塊來恢復(fù)。
塊組描述(Group Discriptor Table):描述塊組屬性信息,也就是管理磁盤塊位圖、inode為徒、inode表、數(shù)據(jù)塊的使用情況。
塊位圖(Block Bitmap):塊位圖中記錄著數(shù)據(jù)塊中哪個(gè)數(shù)據(jù)塊已經(jīng)被占用,哪個(gè)數(shù)據(jù)塊沒有被占用。
inode位圖(inode Bitmap):每個(gè)bite表示該inode編號(hào)是否已經(jīng)被使用,比如0表示沒有被使用,1表示已經(jīng)被使用。
inode表(inode Table):存放文件屬性。如文件大小,所有者,最近修改時(shí)間,使用的塊情況等
數(shù)據(jù)塊(Data Block):存放文件內(nèi)容。
將屬性和數(shù)據(jù)分開存放的想法看起來很簡單,但實(shí)際上是如何工作的呢?我們通過touch一個(gè)新文件來看看如何工作的。
查看inode編號(hào):
ls -li
。
從整體上來看,創(chuàng)建一個(gè)文件所經(jīng)歷的步驟如下:
為了說明問題,我們將上圖簡化:
創(chuàng)建文件一般需要4個(gè)操作:
存儲(chǔ)屬性
內(nèi)核先找到一個(gè)空閑的i節(jié)點(diǎn)(這里是263466)。內(nèi)核把文件信息記錄到其中。
存儲(chǔ)數(shù)據(jù)
該文件需要存儲(chǔ)在三個(gè)磁盤塊,內(nèi)核找到了三個(gè)空閑塊:300,500,800。將內(nèi)核緩沖區(qū)的第一塊數(shù)據(jù)復(fù)制到300,下一塊復(fù)制到500,以此類推。
記錄分配情況
文件內(nèi)容按順序300,500,800存放。內(nèi)核在inode上的磁盤分布區(qū)記錄了上述塊列表。
添加文件名到目錄
新的文件名abc。linux如何在當(dāng)前的目錄中記錄這個(gè)文件?內(nèi)核將入口(263466,abc)(映射關(guān)系)添加到目錄文件。文件名和inode之間的對(duì)應(yīng)關(guān)系將文件名和文件的內(nèi)容及屬性連接起來。
插寫一個(gè)文件系統(tǒng)操作:
使用
dd
命令創(chuàng)建大文件并將其寫入文件系統(tǒng),然后將文件系統(tǒng)掛載到系統(tǒng)中,可以按照以下步驟進(jìn)行:
創(chuàng)建目錄
/path/to
:mkdir -p /path/to
使用 dd 命令創(chuàng)建大文件:
以下命令創(chuàng)建一個(gè)大小為1GB的空文件:
dd if=/dev/zero of=/path/to/largefile bs=1M count=1024
這將在指定路徑創(chuàng)建名為
largefile
的大小為1GB的空文件。使用 mkfs 創(chuàng)建文件系統(tǒng):
在上面創(chuàng)建的文件中,可以使用適當(dāng)?shù)奈募到y(tǒng)類型(例如ext4)來創(chuàng)建文件系統(tǒng)。假設(shè)要?jiǎng)?chuàng)建ext4文件系統(tǒng):
mkfs.ext4 /path/to/largefile
這將在
/path/to/largefile
中創(chuàng)建一個(gè)ext4文件系統(tǒng)。掛載文件系統(tǒng):
創(chuàng)建文件系統(tǒng)后,您可以將其掛載到系統(tǒng)中的某個(gè)目錄。首先,創(chuàng)建一個(gè)用于掛載的目錄:
mkdir /mnt/largefile
然后將文件系統(tǒng)掛載到此目錄:
mount -o loop /path/to/largefile /mnt/largefile
這將將
/path/to/largefile
中的文件系統(tǒng)掛載到/mnt/largefile
目錄中。
5.2、硬鏈接(Hard Link)
- 硬鏈接是指在文件系統(tǒng)中,多個(gè)文件可以指向同一個(gè)inode,這樣多個(gè)文件實(shí)際上指向了相同的數(shù)據(jù)塊。
- 硬鏈接是文件系統(tǒng)級(jí)別的鏈接,它們只是指向了相同的inode,不保留路徑信息,因此即使原文件移動(dòng)或重命名,硬鏈接仍然有效。
- 刪除原文件并不會(huì)影響硬鏈接的可用性,只有當(dāng)所有鏈接都被刪除后,文件的inode和數(shù)據(jù)塊才會(huì)被釋放。
使用硬鏈接:
ln 原文件名 硬鏈接文件名
,如下ln file.txt file.hard
我們前面有講到,文件的一行屬性信息,有一個(gè)是硬鏈接數(shù),我們能看到,一開始沒給file.txt建立硬鏈接的時(shí)候,它的硬鏈接數(shù)1,就是只有自己,當(dāng)給它建立硬鏈接后,硬鏈接的文件和原文件的硬鏈接數(shù)都變?yōu)?。
因此我們得出結(jié)論:普通文件的硬鏈接數(shù)等于該文件的建立的硬鏈接文件個(gè)數(shù)+1(可以自行再給file.txt文件再建立一個(gè)硬鏈接文件,看是不是硬鏈接數(shù)都變?yōu)?)。
我們?cè)倏茨夸浀挠叉溄訑?shù):
我們看到,新創(chuàng)建的目錄就有2個(gè)硬鏈接數(shù)!為什么是兩個(gè)?我們看圖可以知道,一個(gè)是自己的目錄文件的,還有一個(gè)是該目錄下的
.
目錄的,我們知道目錄.
就是指當(dāng)前目錄。我們?cè)賮頊y(cè)試,在該目錄下再創(chuàng)建5個(gè)目錄,再看看硬鏈接數(shù)是多少?
能看到硬鏈接數(shù)變?yōu)?了!為什么呢?我們發(fā)現(xiàn)其中2個(gè)是自己和
.
目錄,還有5個(gè)是該目錄下的目錄(a、b、c、d、e)下的..
目錄,顯而易見..
目錄就是上級(jí)目錄。因此我們得出結(jié)論:目錄文件的硬鏈接數(shù)等于2+該目錄下的目錄個(gè)數(shù)。注意:用戶不能自己給目錄建立硬鏈接,只能由系統(tǒng)創(chuàng)建!因?yàn)樽约簞?chuàng)建目錄的硬鏈接會(huì)導(dǎo)致路徑回路。
假如當(dāng)前目錄是
/user/home
,你給user目錄創(chuàng)建一個(gè)硬鏈接/
,哪當(dāng)前創(chuàng)建的硬鏈接目錄變?yōu)榱?code>/user/home/user,這就導(dǎo)致了路徑回路。
5.3、軟鏈接(Symbolic Link)
軟鏈接也叫符號(hào)鏈接。
- 軟鏈接是一個(gè)特殊的文件,它包含了指向另一個(gè)文件的路徑,相當(dāng)于一個(gè)快捷方式。
- 軟鏈接可以跨越文件系統(tǒng),因?yàn)樗鼈儽4娴氖锹窂叫畔⒍皇莍node號(hào)。
- 軟鏈接不像硬鏈接那樣指向相同的數(shù)據(jù)塊,而是指向目標(biāo)文件的路徑,因此即使原文件移動(dòng)或重命名,軟鏈接也可能失效。
- 刪除原文件后,軟鏈接將成為死鏈接(即指向一個(gè)不存在的目標(biāo)),如果不再需要,可以手動(dòng)刪除。
使用軟鏈接:
ln -s 原文件 軟鏈接文件
。如:ln -s file.txt file.symb
6、動(dòng)態(tài)庫和靜態(tài)庫
下面講解動(dòng)靜態(tài)庫可能會(huì)用到的文件
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;// 當(dāng)前寫入位置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、靜態(tài)庫
靜態(tài)庫(.a文件):程序編譯的時(shí)候把哭的代碼鏈接到可執(zhí)行文件中,程序運(yùn)行時(shí)不再需要靜態(tài)庫。
生成靜態(tài)庫:將.o文件打包生成靜態(tài)庫
使用歸檔工具:
ar -rc 靜態(tài)庫名 .o文件
ar是gnu歸檔工具,rc表示(replace and create)
這里的
.o
文件是用命令gcc -o
生成的(和動(dòng)態(tài)庫不一樣,下面動(dòng)態(tài)庫會(huì)講)需要注意的是:靜態(tài)庫名是有規(guī)范的,不能寫成
XXX.a
,必須寫成libXXX.a
。如:
ar -rc libmylib.a *.o
查看靜態(tài)庫列表:
ar -tv 靜態(tài)庫名
-t:列出靜態(tài)庫中的文件
-v:verbose 詳細(xì)信息
如:
ar -tv libmylib.a
使用靜態(tài)庫:
先將
main.c
生成main.o
文件
- 這里我們使用一個(gè)新命令
gcc -c .c文件
,可以生成同名.o
文件。如gcc -c main.c
。再使用命令:
gcc - o 可執(zhí)行文件 帶main函數(shù)的目標(biāo)文件(如main.o) -L 靜態(tài)庫路徑 -l靜態(tài)庫名(去除前綴lib和后綴.a,如mylib) -static
如
gcc -o myexe main.c -L. -lmylib -static
解釋一下選項(xiàng):
- -L:指定靜態(tài)庫路徑(當(dāng)前路徑可以使用
.
,也可以使用絕對(duì)路徑)- -l:指定靜態(tài)庫
- -static:指定是靜態(tài)鏈接
6.2、動(dòng)態(tài)庫
動(dòng)態(tài)庫相對(duì)靜態(tài)庫的使用更復(fù)雜,原理也更復(fù)雜。
動(dòng)態(tài)庫(.so文件):程序在運(yùn)行的時(shí)候才去鏈接動(dòng)態(tài)庫的代碼,多個(gè)程序共享使用庫的代碼。
一個(gè)與動(dòng)態(tài)庫鏈接的可執(zhí)行文件僅僅包含它用到的函數(shù)入口地址的一個(gè)表,而不是外部函數(shù)所在目標(biāo)文件的整個(gè)機(jī)器碼(不像靜態(tài)庫那樣直接在代碼里面展開)。
在可執(zhí)行文件開始運(yùn)行以前,外部函數(shù)的機(jī)器碼由操作系統(tǒng)從磁盤上的該動(dòng)態(tài)庫中復(fù)制到內(nèi)存中,這個(gè)過程稱為動(dòng)態(tài)鏈接(dynamic linking)
動(dòng)態(tài)庫可以在多個(gè)程序間共享,所以動(dòng)態(tài)鏈接使得可執(zhí)行文件更小,節(jié)省了磁盤空間。操作系統(tǒng)采用虛擬內(nèi)存機(jī)制允許物理內(nèi)存中的一份動(dòng)態(tài)庫被要用到該庫的所有進(jìn)程共用,節(jié)省了內(nèi)存和磁盤空間。
生成動(dòng)態(tài)庫:動(dòng)態(tài)庫的生成也需要目標(biāo)文件,但是動(dòng)態(tài)庫的目標(biāo)文件和靜態(tài)庫的目標(biāo)文件的生成方式不一樣,動(dòng)態(tài)庫目標(biāo)文件必須使用選項(xiàng)
fPIC
(產(chǎn)生位置無關(guān)碼)。
先將
.c
文件生成.o
文件:gcc -c -fPIC *.c
,將.c
文件生成同名.o
文件
再使用選項(xiàng)
shared
將.o
文件打包成動(dòng)態(tài)庫:gcc -shared -o 動(dòng)態(tài)庫名 .o文件
如
gcc -shared -o libmylib.so *.o
需要注意的是:這個(gè)動(dòng)態(tài)庫名也有規(guī)則:不能是
XXX.so
,必須是libXXX.so
。
使用動(dòng)態(tài)庫:
先用main.c文件生成main.o文件
再使用命令
gcc -o 可執(zhí)行文件 帶main函數(shù)的目標(biāo)文件 -L動(dòng)態(tài)庫路徑 -l(動(dòng)態(tài)庫名去掉前綴lib,去掉后綴.so)
如
gcc -o myexe main.o -L. -lmylib
現(xiàn)在有一個(gè)問題:如果動(dòng)態(tài)庫不在當(dāng)前目錄下呢?
我們來測(cè)試一下,將動(dòng)態(tài)庫和頭文件放到一個(gè)目錄下
假設(shè)我們得到了一個(gè)壓縮文件,文件解壓后得到一個(gè)目錄文件,目錄文件里面包含所需所有的頭文件和靜態(tài)庫
這時(shí)候我們?cè)趺催\(yùn)行這個(gè)main函數(shù)呢?
這里我們又會(huì)使用到一個(gè)選項(xiàng)
I
,這個(gè)選項(xiàng)是用來指明頭文件的路徑。
現(xiàn)在出現(xiàn)一個(gè)新的問題!直接運(yùn)行myexe會(huì)報(bào)錯(cuò)!
報(bào)錯(cuò)的原因是找不到動(dòng)態(tài)庫
libmyib.so
。我們來看看該可執(zhí)行文件所依賴的動(dòng)態(tài)庫,使用命令:ldd 可執(zhí)行文件名
。ldd即list dynamic dependence。如
ldd myexe
也就是沒找到該動(dòng)態(tài)庫。有下面四種解決辦法:
1、將該文件所需的動(dòng)態(tài)庫放到系統(tǒng)里的動(dòng)態(tài)庫中(不建議使用,風(fēng)險(xiǎn)高)。
如:
sudo cp mylib/lib/* /lib64
2、使用環(huán)境變量
使用命令:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:動(dòng)態(tài)庫文件路徑
。這樣導(dǎo)入環(huán)境變量的話,重啟shell又需要重新配置。如:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/xp2/Linux_Primary/day_32/demo_02/dynamiclib/user/mylib/lib
還可以去系統(tǒng)文件添加環(huán)境變量
在這兩個(gè)文件中:
.bash_profile
和.bashrc
文件。查找到這兩個(gè)文件的方式就是先cd到用戶路徑cd ~
,再ls -al
查看這個(gè)路徑下所有文件就可以看到這兩個(gè)文件。
3、使用軟鏈接的方式(推薦,簡單好用)
在lib64目錄下新建一個(gè)同動(dòng)態(tài)庫名的動(dòng)態(tài)庫文件的軟鏈接。
如:
sudo ln -s /home/xp2/Linux_Primary/day_32/demo_02/dynamiclib/user/mylib/lib/libmylib.so /lib64/libmylib.so
4、在系統(tǒng)目錄下(/etc/ld.so.conf.d/)新建一個(gè)(文件名.conf)配置文件(推薦)
那么好,Linux基礎(chǔ)IO就到這里,如果你對(duì)Linux和C++也感興趣的話,可以看看我的主頁哦。下面是我的github主頁,里面記錄了我的學(xué)習(xí)代碼和leetcode的一些題的題解,有興趣的可以看看。
Xpccccc的github主頁