C文件接口
C文件接口
C文件接口都是封裝了系統(tǒng)的文件接口,學(xué)習(xí)系統(tǒng)的文件接口有利于更熟悉文件的操作。
系統(tǒng)文件I/O
open函數(shù)
頭文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
函數(shù)描述
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
參數(shù)
pathname: 要打開或創(chuàng)建的目標(biāo)文件
flags: 打開文件時(shí),可以傳入多個(gè)參數(shù)選項(xiàng),用下面的一個(gè)或者多個(gè)常量進(jìn)行“或”運(yùn)算,構(gòu)成flags。
參數(shù):
O_RDONLY: 只讀打開
O_WRONLY: 只寫打開
O_RDWR : 讀,寫打開
這三個(gè)常量,必須指定一個(gè)且只能指定一個(gè)
O_CREAT : 若文件不存在,則創(chuàng)建它。需要使用mode選項(xiàng),來指明新文件的訪問權(quán)限
O_APPEND: 追加寫
返回值:
成功:新打開的文件描述符
失?。?1
open函數(shù)具體使用哪個(gè),和具體應(yīng)用場景相關(guān),如目標(biāo)文件不存在,則第三個(gè)參數(shù)表示創(chuàng)建文件的默認(rèn)權(quán)限,否則只使用前兩個(gè)參數(shù)。
打開文件的本質(zhì)就是將需要的文件屬性加載到內(nèi)存中,OS內(nèi)部一定會(huì)存在大量被打開的文件,那么操作系統(tǒng)如何管理被打開的文件?先描述,再組織。
先描述,構(gòu)建在內(nèi)存中的文件結(jié)構(gòu)體struct file(文件屬性,struct file*),每一個(gè)被打開的文件,都要在OS內(nèi)構(gòu)建對(duì)應(yīng)的文件對(duì)象的struct結(jié)構(gòu)體,再組織,即將所有的struct file結(jié)構(gòu)體使用某種數(shù)據(jù)結(jié)構(gòu)如鏈表去鏈接起來。
在OS內(nèi)部,對(duì)被打開的文件進(jìn)行管理,就被轉(zhuǎn)換為了對(duì)鏈表的增刪查改。
結(jié)論:文件被打開,OS要為被打開的文件,構(gòu)建對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)。
struct file
{
//各自屬性
//各種鏈接關(guān)系
};
文件可以分為兩大類:磁盤文件,被打開的文件(內(nèi)存文件)。
文件被打開,是誰在打開呢?OS,但是是誰讓打開的?用戶(進(jìn)程為代表的),我們之前的所有文件操作,都是進(jìn)程和被打開文件的關(guān)系,即struct task_struct 和 struct file的關(guān)系。
open函數(shù)的使用例子
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <cerrno>
#include <string.h>
#include <unistd.h>
#include <iostream>
#define LOG "log.txt"
int main()
{
int fd = open(LOG,O_WRONLY);//打開文件,表示只進(jìn)行寫入,如果沒有該文件,會(huì)打開失敗
if(fd == -1)
{
printf("errno : %d , error : %s\n",errno,strerror(errno));
exit(-1);
}
close(fd);
return 0;
}
沒有l(wèi)og.txt文件,打開失敗。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <cerrno>
#include <string.h>
#include <unistd.h>
#include <iostream>
#define LOG "log.txt"
int main()
{
int fd = open(LOG,O_WRONLY | O_CREAT);//打開文件,表示只進(jìn)行寫入,加入標(biāo)準(zhǔn)O_CREAT表示沒有該文件就進(jìn)行創(chuàng)建
if(fd == -1)
{
printf("errno : %d , error : %s\n",errno,strerror(errno));
exit(-1);
}
close(fd);
return 0;
}
雖然open函數(shù)創(chuàng)建了log.txt文件,但是ls顯示的時(shí)候,該文件卻標(biāo)紅,這是為什么呢?這是在創(chuàng)建的時(shí)候,沒有設(shè)置權(quán)限,導(dǎo)致權(quán)限出現(xiàn)了亂碼。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <cerrno>
#include <string.h>
#include <unistd.h>
#include <iostream>
#define LOG "log.txt"
int main()
{
int fd = open(LOG,O_WRONLY | O_CREAT,0666);//打開文件,表示只進(jìn)行寫入,加入標(biāo)準(zhǔn)O_CREAT表示沒有該文件就進(jìn)行創(chuàng)建
if(fd == -1) //加入權(quán)限0666
{
printf("errno : %d , error : %s\n",errno,strerror(errno));
exit(-1);
}
close(fd);
return 0;
}
此時(shí),創(chuàng)建成功,權(quán)限也不會(huì)出現(xiàn)亂碼,但是權(quán)限卻是664,而不是666,這又是受到權(quán)限掩碼的影響。
詳細(xì)看文章的文件權(quán)限
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <cerrno>
#include <string.h>
#include <unistd.h>
#include <iostream>
#define LOG "log.txt"
int main()
{
umask(0);//這里將權(quán)限掩碼設(shè)置為0即可
int fd = open(LOG,O_WRONLY | O_CREAT,0666);//打開文件,表示只進(jìn)行寫入,加入標(biāo)準(zhǔn)O_CREAT表示沒有該文件就進(jìn)行創(chuàng)建
if(fd == -1) //加入權(quán)限0666
{
printf("errno : %d , error : %s\n",errno,strerror(errno));
exit(-1);
}
close(fd);
return 0;
}
這里的權(quán)限就是666了。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <cerrno>
#include <string.h>
#include <unistd.h>
#include <iostream>
#define LOG "log.txt"
int main()
{
umask(0);//這里將權(quán)限掩碼設(shè)置為0即可
int fd = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);//打開文件,表示只進(jìn)行寫入,加入標(biāo)準(zhǔn)O_CREAT表示沒有該文件就進(jìn)行創(chuàng)建
if(fd == -1) //加入權(quán)限0666,加入O_TRUNC表示打開文件時(shí)會(huì)對(duì)文件進(jìn)行清空
{
printf("errno : %d , error : %s\n",errno,strerror(errno));
exit(-1);
}
close(fd);
return 0;
}
O_TRUNC表示每次打卡文件都會(huì)進(jìn)行清空,它會(huì)O_APPEND即追加不會(huì)同時(shí)出現(xiàn),因?yàn)閮烧呦噙`。
open函數(shù)的flag標(biāo)志
open函數(shù)的flag標(biāo)志本質(zhì)是一個(gè)位圖結(jié)構(gòu),比如一個(gè)int就可以同時(shí)傳遞32個(gè)標(biāo)記位,操作系統(tǒng)會(huì)去遍歷這個(gè)位圖,查看哪個(gè)位置是1,再進(jìn)行相應(yīng)的操作。比如是否清空,就表示位圖某個(gè)位置是否置1。
系統(tǒng)調(diào)用和庫函數(shù)
fopen fclose fread fwrite 都是C標(biāo)準(zhǔn)庫當(dāng)中的函數(shù),我們稱之為庫函數(shù)(libc),而 open close read write lseek 都屬于系統(tǒng)提供的接口,稱之為系統(tǒng)調(diào)用口。
所以,可以認(rèn)為,f#系列的函數(shù),都是對(duì)系統(tǒng)調(diào)用的封裝,方便二次開發(fā)。
這些函數(shù)直接man指令查詢即可,與C庫文件操作大同小異。
文件描述符
通過對(duì)open函數(shù)的學(xué)習(xí),我們知道了文件描述符就是一個(gè)小整數(shù)
0 & 1 & 2
Linux進(jìn)程默認(rèn)情況下會(huì)有3個(gè)缺省打開的文件描述符,分別是標(biāo)準(zhǔn)輸入0, 標(biāo)準(zhǔn)輸出1, 標(biāo)準(zhǔn)錯(cuò)誤2。0,1,2對(duì)應(yīng)的物理設(shè)備一般是:鍵盤,顯示器,顯示器。
任何一個(gè)進(jìn)程,在啟動(dòng)的時(shí)候,默認(rèn)會(huì)打開當(dāng)前進(jìn)程的三個(gè)文件。
C語言中:
extern FILE* stdin
extern FILE* stdout
extern FILE* stderr
我們打開一個(gè)文件,可以發(fā)現(xiàn)文件描述符是從3開始的。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <cerrno>
#include <string.h>
#include <unistd.h>
#include <iostream>
#define LOG "log.txt"
int main()
{
umask(0);//這里將權(quán)限掩碼設(shè)置為
int fd = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
if(fd == -1)
{
printf("errno : %d , error : %s\n",errno,strerror(errno));
exit(-1);
}
printf("文件描述符:%d\n",fd);
close(fd);
return 0;
}
為什么文件描述符從3開始呢,0、1、2代表著什么,三個(gè)默認(rèn)打開的文件流。
Linux下一切皆文件,所以向顯示器打印,本質(zhì)是向文件寫入。
#include <cstdio>
#include <iostream>
int main()
{
printf("hello printf->stdout\n");
fprintf(stdout,"hello fprintf->stdout\n");
fprintf(stderr,"hello fprintf->stderr\n");
//c++
std::cout << "hello cout->cout" << std::endl;
std::cerr << "hello cerr->cerr" << std::endl;
return 0;
}
標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)錯(cuò)誤都會(huì)向顯示器打印,但是其實(shí)是不一樣的。
標(biāo)準(zhǔn)輸出會(huì)受到重定向的影響,標(biāo)準(zhǔn)錯(cuò)誤不會(huì)受到重定向的影響。
進(jì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)的文件。
如何理解Linux下一切皆文件?
我們使用OS的本質(zhì):都是通過進(jìn)程的方式進(jìn)行OS的訪問的。
在所有的外設(shè)上鋪上一層軟件層,即寫函數(shù)和讀函數(shù),根據(jù)需要來設(shè)計(jì)這些函數(shù),在上層,用struct file來管理這些函數(shù),所以在Linux看來,一切皆文件。
FILE和fd的關(guān)系
FILE是什么?結(jié)構(gòu)體。
誰提供的?C語言提供的。
和我們剛才提供的內(nèi)核struct file有關(guān)系?——沒有任何關(guān)系,上下層關(guān)系。
文件描述符的分配規(guī)則
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <cerrno>
#include <string.h>
#include <unistd.h>
#include <iostream>
#define LOG "log.txt"
int main()
{
umask(0);//這里將權(quán)限掩碼設(shè)置為
int fd1 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
int fd2 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
int fd3 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
int fd4 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
int fd5 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
int fd6 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
printf("%d\n",fd1);
printf("%d\n",fd2);
printf("%d\n",fd3);
printf("%d\n",fd4);
printf("%d\n",fd5);
printf("%d\n",fd6);
return 0;
}
因?yàn)?、1、2是默認(rèn)打開的三個(gè)文件流,所以是文件描述符是從3開始打開的。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <cerrno>
#include <string.h>
#include <unistd.h>
#include <iostream>
#define LOG "log.txt"
int main()
{
umask(0);//這里將權(quán)限掩碼設(shè)置為
close(0);//關(guān)閉0號(hào)文件描述符
int fd1 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
int fd2 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
int fd3 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
int fd4 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
int fd5 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
int fd6 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
printf("%d\n",fd1);
printf("%d\n",fd2);
printf("%d\n",fd3);
printf("%d\n",fd4);
printf("%d\n",fd5);
printf("%d\n",fd6);
return 0;
}
關(guān)閉0號(hào)文件描述符,程序運(yùn)行的結(jié)果:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <cerrno>
#include <string.h>
#include <unistd.h>
#include <iostream>
#define LOG "log.txt"
int main()
{
umask(0);//這里將權(quán)限掩碼設(shè)置為
close(0);//關(guān)閉0號(hào)文件描述符
close(2);//關(guān)閉2號(hào)文件描述符
int fd1 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
int fd2 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
int fd3 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
int fd4 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
int fd5 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
int fd6 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
printf("%d\n",fd1);
printf("%d\n",fd2);
printf("%d\n",fd3);
printf("%d\n",fd4);
printf("%d\n",fd5);
printf("%d\n",fd6);
return 0;
}
關(guān)閉0號(hào)文件描述符和2號(hào)描述符,程序運(yùn)行的結(jié)果:
進(jìn)程中,文件描述符的分配規(guī)則:在文件描述符表中,最小的沒有被使用的數(shù)組元素,分配給新文件。
重定向
重定向的本質(zhì)
輸出重定向
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <cerrno>
#include <string.h>
#include <unistd.h>
#include <iostream>
#define LOG "log.txt"
int main()
{
umask(0);//這里將權(quán)限掩碼設(shè)置為
close(1);//關(guān)閉1號(hào)文件描述符
int fd = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
printf("you can see me!\n");
printf("you can see me!\n");
printf("you can see me!\n");
printf("you can see me!\n");
printf("you can see me!\n");
printf("you can see me!\n");
printf("you can see me!\n");
printf("you can see me!\n");
printf("you can see me!\n");
printf("you can see me!\n");
return 0;
}
程序運(yùn)行之后并沒有結(jié)果,但是打印log.txt文件內(nèi)容的時(shí)候,卻發(fā)現(xiàn)了程序運(yùn)行的結(jié)果。
這其實(shí)已經(jīng)發(fā)生重定向。
程序首先關(guān)閉了1號(hào)文件,再打開log.txt,根據(jù)文件描述符的分配規(guī)則,log.txt將被分配到1號(hào)文件描述符。
printf往文件描述符為1(即標(biāo)準(zhǔn)輸出)進(jìn)行打印,但是底層文件描述符為1的指向從標(biāo)準(zhǔn)輸出被替換為log.txt,所以出現(xiàn)了不往顯示器打印,而是往log.txt文件輸出。(重定向的本質(zhì))
重定向的原理:在上層無法感知的情況下,在OS內(nèi)部,更改進(jìn)程對(duì)應(yīng)的文件描述符表中的下標(biāo)的指向。
輸入重定向
將log.txt文件改為123 456
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <cerrno>
#include <string.h>
#include <unistd.h>
#include <iostream>
#define LOG "log.txt"
int main()
{
umask(0);//這里將權(quán)限掩碼設(shè)置為
close(0);//關(guān)閉0號(hào)文件描述符
int fd = open(LOG,O_RDONLY,0666);//注意改為只讀
int a,b;
scanf("%d %d",&a,&b);
printf("a=%d,b=%d\n",a,b);
return 0;
}
原理一樣,關(guān)閉0號(hào)文件描述符,打開log.txt文件,那么0號(hào)文件描述符就指向log.txt文件,即從log.txt獲取結(jié)果輸入到變量a和變量b中。
圖形跟上一張圖差不多。只不過是標(biāo)準(zhǔn)輸入指向的新文件。
追加重定向
讓標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤分開輸出到不同文件,方便排查錯(cuò)誤。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <cerrno>
#include <string.h>
#include <unistd.h>
#include <iostream>
#define LOG_NORWAL "logNormal.txt"
#define LOG_ERROR "logERROR.txt"
int main()
{
umask(0);//這里將權(quán)限掩碼設(shè)置為
close(1);//關(guān)閉0號(hào)文件描述符
open(LOG_NORWAL,O_WRONLY | O_CREAT | O_APPEND,0666);//注意改為追加
close(2);
open(LOG_ERROR,O_WRONLY | O_CREAT | O_APPEND,0666);//注意改為追加
printf("printf->stdout\n");//正確結(jié)果
fprintf(stdout,"fprintf->stdout\n");//正確結(jié)果
fprintf(stderr,"fprintf->stderr\n");//錯(cuò)誤
return 0;
}
重定向也可以通過指令來實(shí)現(xiàn),常見的重定向有:>, >>, <。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <cerrno>
#include <string.h>
#include <unistd.h>
#include <iostream>
int main()
{
printf("printf->stdout\n");//正確結(jié)果
fprintf(stdout,"fprintf->stdout\n");//正確結(jié)果
fprintf(stderr,"fprintf->stderr\n");//錯(cuò)誤
return 0;
}
stdout、cout都是向1號(hào)文件描述符對(duì)應(yīng)的文件打印。
stderr、cerr都是向2號(hào)文件描述符對(duì)應(yīng)的文件打印。
dup2函數(shù)
dup、dup2、dup3函數(shù)都是改變文件描述符的指向。
dup2(3,1);//將文件描述符1指向改為與文件描述符3指向相同,關(guān)閉文件描述符3的指向。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <cerrno>
#include <string.h>
#include <unistd.h>
#include <iostream>
#define LOG "log.txt"
int main()
{
umask(0);
int fd = open(LOG,O_CREAT | O_WRONLY | O_APPEND,0666);
if(fd < 0)
{
perror("open");
exit(1);
}
dup2(fd,1);//將文件描述符1的指向改成與fd指向相同,關(guān)閉文件描述符fd的指向,間接完成了輸出重定向
printf("hello world\n");
close(fd);
return 0;
}
FILE
我們先看一個(gè)程序。
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#define LOG "log.txt"
int main()
{
fprintf(stdout,"hello fprintf\n");
const char* msg = "hello write\n";
write(1,msg,strlen(msg));
fork();
return 0;
}
正常運(yùn)行下:
輸出重定向下:
輸出重定向下,fprintf居然多打印了一次。這是為什么呢?
C語言會(huì)結(jié)合一定刷新策略,將我們?cè)贑庫緩沖區(qū)中的數(shù)據(jù)寫到files_struct[fd]指向的緩沖區(qū),再由OS結(jié)合一定的刷新策略,將files_struct[fd]指向的緩沖區(qū)寫到磁盤上。
C語言的刷新策略:
1.無緩沖
2.行緩沖(遇到\n就刷新)
3.全緩沖(緩沖區(qū)滿了就刷新)
1.顯示器采用的刷新策略:行緩沖。
2.普通文件采用的刷新策略:全緩沖。
3.緩沖區(qū)在哪里,在你進(jìn)行fopen打開文件的時(shí)候,你會(huì)得到FILE結(jié)構(gòu)體,緩沖區(qū)就在這個(gè)FILE結(jié)構(gòu)體中。
4.為什么要有緩沖區(qū),節(jié)省調(diào)用者的時(shí)間(調(diào)用刷新函數(shù)是系統(tǒng)調(diào)用)(系統(tǒng)調(diào)用也要花費(fèi)時(shí)間)(減少頻繁調(diào)用,采用一次性刷新大量數(shù)據(jù))。
解釋現(xiàn)象
一開始只是運(yùn)行程序,未重定向,結(jié)果為hello fprintf和hello write。
原因:fprintf是輸入到顯示器,顯示器是行刷新,所以當(dāng)把hello fprintf輸入到C庫中的緩沖區(qū)中,因?yàn)橛小痋n’,所以會(huì)直接刷新到操作系統(tǒng)的緩沖區(qū)中,而write是系統(tǒng)調(diào)用,會(huì)直接刷新到操作系統(tǒng)的緩沖區(qū)中,綜上,當(dāng)fork的時(shí)候,C庫的輸出緩沖區(qū)已經(jīng)沒有內(nèi)容了。
當(dāng)輸出重定向到普通文件時(shí),普通文件是全刷新,而一行的hello fprintf\n并不會(huì)使C庫的輸出緩沖區(qū)滿了,所以hello fprintf\n不會(huì)被刷新到操作系統(tǒng)的緩沖區(qū)中,而write是系統(tǒng)調(diào)用,會(huì)直接刷新到系統(tǒng)的緩沖區(qū)中,所以,fork的時(shí)候,C庫的緩沖區(qū)還要內(nèi)容hello fprintf\n,當(dāng)fork的時(shí)候,C庫的緩沖區(qū)還有內(nèi)容hello fprintf\n,那么父子進(jìn)程誰先進(jìn)行刷新C庫的緩沖區(qū),誰先發(fā)生寫實(shí)拷貝,那么父子進(jìn)程各刷新一次,就會(huì)輸出兩次hello fprintf\n。
綜上: printf fwrite 庫函數(shù)會(huì)自帶緩沖區(qū),而 write 系統(tǒng)調(diào)用沒有帶緩沖區(qū)。另外,我們這里所說的緩沖區(qū),都是用戶級(jí)緩沖區(qū)。其實(shí)為了提升整機(jī)性能,OS也會(huì)提供相關(guān)內(nèi)核級(jí)緩沖區(qū),不過不再我們討論范圍之內(nèi)。那這個(gè)緩沖區(qū)誰提供呢? printf fwrite 是庫函數(shù), write 是系統(tǒng)調(diào)用,庫函數(shù)在系統(tǒng)調(diào)用的“上層”, 是對(duì)系統(tǒng)調(diào)用的“封裝”,但是 write 沒有緩沖區(qū),而 printf fwrite 有,足以說明,該緩沖區(qū)是二次加上的,又因?yàn)槭荂,所以由C標(biāo)準(zhǔn)庫提供。
前面已經(jīng)說過,由OS結(jié)合一定的刷新策略,將files_struct[fd]指向的緩沖區(qū)寫到磁盤上,那么,我們?cè)趺磸?qiáng)制刷新內(nèi)核緩沖區(qū)呢?使用fsync函數(shù),就可以強(qiáng)制內(nèi)核緩沖區(qū)刷新數(shù)據(jù)到磁盤上。
int fsync(int fd);
理解文件系統(tǒng)
如果文件沒有被打開呢?那么文件會(huì)在哪里?一定不在內(nèi)存中,只能在磁盤等外設(shè)中靜靜的存儲(chǔ)著。
磁盤文件,如何沒有被打開,如何理解這些文件呢?需要解決什么問題?
需要解決合理存儲(chǔ)的問題,快速定位,快速讀取和寫入等問題。
了解磁盤的物理結(jié)構(gòu)
磁盤分為機(jī)械磁盤和SSD固態(tài)磁盤。
磁盤是計(jì)算機(jī)唯一的一個(gè)機(jī)械設(shè)備,同時(shí)它還是外設(shè)。外設(shè)和機(jī)械設(shè)備決定了它非常慢(相對(duì)CPU和內(nèi)存來說)。
盤片:一片兩面,有一摞盤片,可以讀寫。
向磁盤寫入:盤片的某一些位置N->S
刪除磁盤數(shù)據(jù):磁盤的某一些位置S->N
磁頭:一個(gè)面一個(gè)磁頭,一個(gè)磁頭負(fù)責(zé)一面的讀取
磁頭:共進(jìn)退的。
磁頭和盤面是沒有挨著的,距離依舊很近。
對(duì)數(shù)據(jù)的寫入和讀取分別是更改某個(gè)位置的S、N或者讀取某個(gè)位置的S、N。
磁盤中存儲(chǔ)的基本元素:扇區(qū),512字節(jié)或者4kb字節(jié),一般磁盤,所有的扇區(qū)都是512字節(jié)。
同半徑的所有扇區(qū),稱為磁道。
如何定位一個(gè)扇區(qū)?
首先確定哪一面,這里只需要確定哪一個(gè)磁頭的編號(hào)來確定是哪一面。
再定位哪一個(gè)磁道,由半徑?jīng)Q定。
再確定哪一個(gè)扇區(qū),根據(jù)扇區(qū)的編號(hào),確定一個(gè)扇區(qū)。
磁頭:head
柱面(磁道):cylinder
扇區(qū):sector
所以上面確定扇區(qū)的辦法為:CHS定位法
一個(gè)普通文件(屬性和數(shù)據(jù)),都是數(shù)據(jù)(0,1),無非就是占用一個(gè)或者多個(gè)扇區(qū),來進(jìn)行自己的數(shù)據(jù)存儲(chǔ)的。既然能用CHS方法定位任意一個(gè)扇區(qū),就可以定位任意多個(gè)扇區(qū),從而將文件從硬件角度,進(jìn)行讀取或者寫入。
邏輯抽象
如果OS能夠得知任意一個(gè)CHS地址,就能訪問任意一個(gè)扇區(qū)。那么OS內(nèi)部是直接使用CHS地址的么?不是。
1.OS是軟件,磁盤是硬件,磁盤定位一個(gè)地址,CHS,OS使用的話,如果硬件變了,OS也要發(fā)生變化,OS和磁盤沒有做好解耦的工作。
2。即使是扇區(qū),512字節(jié),定位的IO的基本數(shù)據(jù)也就很小,OS基本單位是4kb(可以調(diào)整),磁盤:塊設(shè)備,所以,OS要有一套新的地址,來進(jìn)行塊設(shè)備級(jí)別的訪問。
OS是以4kb為單位進(jìn)行IO的,故一個(gè)OS級(jí)別的文件塊要包括8個(gè)扇區(qū),甚至,在OS角度,它不關(guān)心扇區(qū)。
計(jì)算機(jī)常規(guī)的訪問方式:起始地址+偏移量(語言中的數(shù)據(jù)類型),只需要找到數(shù)據(jù)塊的起始地址(第一個(gè)扇區(qū)的下標(biāo)地址)+4kb(塊的類型),就可以定位一個(gè)數(shù)據(jù)塊。
OS采用LBA(邏輯塊地址),磁盤采用CHS地址,所以一直存在LBA和CHS相互轉(zhuǎn)換。
文件系統(tǒng)
文件系統(tǒng)的圖解和解析
Boot Block:保存與操作系統(tǒng)啟動(dòng)相關(guān)的內(nèi)容,諸如分區(qū)表和操作系統(tǒng)的鏡像的地址,一般該分區(qū)會(huì)在0號(hào)盤面的0號(hào)磁道的1號(hào)扇區(qū)里開始保存,對(duì)應(yīng)的一般在C盤的某個(gè)位置存在這樣的數(shù)據(jù),當(dāng)操作系統(tǒng)開機(jī)的時(shí)候,至少要做兩件事情,第一個(gè),找到磁盤這個(gè)設(shè)備,并加載磁盤的驅(qū)動(dòng)程序,第二個(gè)是加載分區(qū)表,識(shí)別出磁盤的C盤、D盤、F盤,再從分區(qū)的起始位置讀取操作系統(tǒng)的地址,然后找到操作系統(tǒng)在磁盤的位置,再加載操作系統(tǒng)。如果這個(gè)塊因?yàn)槟骋恍┰驅(qū)е聰?shù)據(jù)丟失,比如刮花了,那么操作系統(tǒng)就會(huì)掛掉。
Block Group:ext2文件系統(tǒng)會(huì)根據(jù)分區(qū)的大小劃分為數(shù)個(gè)Block Group。而每個(gè)Block Group都有著相同的結(jié)構(gòu)組成。
超級(jí)塊(Super Block):存放文件系統(tǒng)本身的結(jié)構(gòu)信息。記錄的信息主要有: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)就被破壞了??偨Y(jié):1.文件系統(tǒng)的類型 2.整個(gè)分組的情況。
GDT,Group Descriptor Table:塊組描述符,描述塊組屬性信息
塊位圖(Block Bitmap):Block Bitmap中記錄著Data Block中哪個(gè)數(shù)據(jù)塊已經(jīng)被占用,哪個(gè)數(shù)據(jù)塊沒有被占用
inode位圖(inode Bitmap):每個(gè)bit表示一個(gè)inode是否空閑可用。
i節(jié)點(diǎn)表(inode Table):存放文件屬性 如 文件大小,所有者,最近修改時(shí)間等
數(shù)據(jù)區(qū):存放文件內(nèi)容
一般而言,一個(gè)文件內(nèi)部所有屬性的集合為inode節(jié)點(diǎn)(128字節(jié)),一個(gè)文件,一個(gè)inode,其中,即使是一個(gè)分區(qū),內(nèi)部也會(huì)存在大量的文件即會(huì)存在大量的inode節(jié)點(diǎn),一個(gè)group,需要有一個(gè)區(qū)域,來專門保存該group內(nèi)所有的inode節(jié)點(diǎn)——inode table。
分組內(nèi)部,可能會(huì)存在多個(gè)inode,需要將inode區(qū)分開來,每一個(gè)inode都會(huì)有自己的inode編號(hào),inode編號(hào),也屬于對(duì)應(yīng)文件的屬性id。
文件的內(nèi)容是變化的。文件系統(tǒng)采用數(shù)據(jù)塊來進(jìn)行文件內(nèi)容的保存,所以一個(gè)有效的文件,要保存內(nèi)容,就需要[1,n]個(gè)數(shù)據(jù)塊。
linux查找一個(gè)文件,是要根據(jù)inode編號(hào),來進(jìn)行文件查找的,包括讀取內(nèi)容。
一個(gè)inode對(duì)應(yīng)一個(gè)文件,而該文件inode屬性和該文件的數(shù)據(jù)塊是有映射關(guān)系的。
文件=內(nèi)容+屬性
linux是將內(nèi)容和屬性分離的,都是以塊的形式,被保存在磁盤的某個(gè)位置的。
數(shù)組代表了該文件對(duì)應(yīng)的數(shù)據(jù)塊的編號(hào),比如Data blocks依次保存了該文件的位置是1、3、5、7、9,那么該數(shù)組存的是1、3、5、7、9。
操作系統(tǒng)加載一個(gè)文件的時(shí)候,一次會(huì)加載許多的inode Table,因?yàn)樗笮」潭ǎ⑶冶容^小,加載進(jìn)來以后,訪問哪個(gè)文件不著急,因?yàn)閕node Table有跟文件的映射關(guān)系,需要讀取文件的哪一塊,再根據(jù)inode屬性里面的數(shù)組,在塊里面去找,拿到lba轉(zhuǎn)換成chs去訪問磁盤。
ls -il //查看文件的inode
通過文件系統(tǒng)來理解ls -al
ls -al查文件,那么文件的查是怎么操作的呢?當(dāng)ls進(jìn)程跑起來,它所處的路徑和目錄已知,那么通過該目錄(目錄也是文件)的inode與數(shù)據(jù)塊的對(duì)應(yīng)關(guān)系,找到該目錄存儲(chǔ)數(shù)據(jù)的數(shù)據(jù)塊,找到對(duì)應(yīng)的文件名和inode的映射關(guān)系,找到inode,然后在自己特定的分區(qū)中找到對(duì)應(yīng)的inode數(shù)據(jù)。
目錄的數(shù)據(jù)塊存儲(chǔ)著該目錄下的文件和inode的對(duì)應(yīng)關(guān)系。
- inode vs 文件名
linux系統(tǒng)只認(rèn)識(shí)inode編號(hào),文件的inode屬性中,并不存在文件名,文件名,只是給用戶用的。 - 重新認(rèn)識(shí)目錄
目錄是文件嗎?是的。目錄有inode嗎?有。
有內(nèi)容嗎?有。
內(nèi)容是什么? - 任何一個(gè)文件,一定在一個(gè)目錄的內(nèi)部,所以目錄的內(nèi)容是什么呢?存儲(chǔ)需要數(shù)據(jù)塊,目錄的數(shù)據(jù)塊里面保存的是該目錄下的文件名和文件inode編號(hào)的對(duì)應(yīng)的映射關(guān)系,而且,在目錄內(nèi),文件名和inode互為key值。
- 當(dāng)我們?cè)L問一個(gè)文件的時(shí)候,我們是在特定的目錄下訪問的 cat log.txt
a.先要在當(dāng)前目錄下,找到log.txt的inode編號(hào)。
b.一個(gè)目錄也是一個(gè)文件,也一定隸屬于一個(gè)分區(qū),結(jié)合inode,在該分區(qū)中找到分組,在該分組中inode table中,找到文件的inode。
c.通過inode和對(duì)應(yīng)的data blocks的映射關(guān)系,找到該文件的數(shù)據(jù)塊,并加載到OS,并完全顯示到顯示器。
通過文件系統(tǒng)來理解文件增刪查改
刪除一個(gè)文件
1.根據(jù)文件名在該文件所在目錄的數(shù)據(jù)塊找到對(duì)應(yīng)的inode編號(hào)
2.經(jīng)過inode與data blocks的映射關(guān)系,設(shè)置block bitmap對(duì)應(yīng)的比特位,置0就可以
3.inode,設(shè)置對(duì)應(yīng)的inode bitmap對(duì)應(yīng)的比特位為0
總結(jié):刪除文件只需要修改位圖即可。
增加一個(gè)文件
當(dāng)增加文件的時(shí)候,首先在一個(gè)特定的目錄下創(chuàng)建,操作系統(tǒng)就要在該目錄所在某一個(gè)分組中,先去查inode bitmap,從低比特位向高比特位掃描,找到為0的比特位,把它置為1,置1的同時(shí)根據(jù)是第幾個(gè)比特位,得到新的inode編號(hào),然后把文件相關(guān)的屬性填到inode Table中的inode表中,文件剛開始是空的,所以沒有數(shù)據(jù)塊,找到目錄的數(shù)據(jù)塊,追加一條新的文件名和inode的映射關(guān)系,然后這個(gè)文件就創(chuàng)建好了,未來你想對(duì)文件做修改和寫入的時(shí)候,拿到文件名找映射關(guān)系,然后找到該文件的inode,分配Data blocks,操作系統(tǒng)就會(huì)即使想要刷新的數(shù)據(jù)有多大,再申請(qǐng)若干個(gè)數(shù)據(jù)塊,并把block bitmap對(duì)應(yīng)的位置置為1,最后進(jìn)行刷新數(shù)據(jù)。
查找一個(gè)文件
如find指令,操作系統(tǒng)會(huì)在特定的目錄下,通過該目錄的inode與data blocks的映射關(guān)系,找到對(duì)應(yīng)的數(shù)據(jù)塊,可查看該目錄下的inode和文件名的映射關(guān)系,找到想要查找的文件,便可以得知該文件的屬性(inode)和內(nèi)容(對(duì)應(yīng)的data blocks)。
修改一個(gè)文件
在特定的目錄下,通過該目錄的inode與data blocks的映射關(guān)系,找到該目錄下的數(shù)據(jù)塊,可查到該目錄下inode和文件名的映射關(guān)系,查到想要修改的文件的inode,通過inode與data blocks的對(duì)應(yīng)關(guān)系,找到該文件的數(shù)據(jù)塊,便可以進(jìn)行修改,修改后,操作系統(tǒng)自動(dòng)修正inode的屬性。
補(bǔ)充細(xì)節(jié)
-
如果文件被誤刪了,該怎么辦?
文件被誤刪了,那么就不要?jiǎng)?chuàng)建新的文件和目錄,因?yàn)楸徽`刪文件的位圖由1變?yōu)?,意味著該文件對(duì)應(yīng)的inode節(jié)點(diǎn)和數(shù)據(jù)塊隨時(shí)都會(huì)被新的文件占用,所以文件誤刪,最好的做法就是什么都不要干(即不要把數(shù)據(jù)和屬性覆蓋),如果能力允許的話,利用一些工具,找到被刪文件的inode編號(hào),通過inode編號(hào)找到特定的分組,然后將inode bitmap對(duì)應(yīng)的比特位由0置為1,然后讀取inode表,提取讀取文件占有的數(shù)據(jù)塊,然后將對(duì)應(yīng)的數(shù)據(jù)塊的block bitmap置1。 -
inode,確定分組,inode是在一個(gè)分區(qū)唯一有效的,不能跨分區(qū)。(每個(gè)分區(qū)都有自己的文件系統(tǒng))
-
分區(qū)和分組,填寫系統(tǒng)屬性是誰做的呢?OS做的。什么時(shí)候做的呢?分區(qū)完成之后,要讓分區(qū)能夠被正常使用,我們需要對(duì)分區(qū)做格式化處理。格式化的過程,其實(shí)是OS向分區(qū)寫入文件系統(tǒng)的管理屬性信息。
-
如果inode只是單單的用數(shù)組建立和data block的映射關(guān)系,15 * 4kb = 60kb(假設(shè)一個(gè)datablock數(shù)組有15個(gè)元素),是不是意味著一個(gè)文件最多放入60kb。
格式化時(shí),如果整個(gè)分區(qū)從來沒有格式化過,那么格式化,需要去寫數(shù)據(jù)區(qū)(從SB到Data blocks),如果當(dāng)前有數(shù)據(jù)區(qū),格式化的時(shí)候只需要將位圖結(jié)構(gòu)清空,屬性字段(sb、gdt)設(shè)置為初始狀態(tài)就可以了。inode table和data blocks不管。
Linux對(duì)于刪除文件會(huì)有一個(gè)日志,該日志會(huì)記錄文件名和inode,這個(gè)日志會(huì)保留一段時(shí)間。
每個(gè)分區(qū)的都有一套inode機(jī)制,inode不能跨分區(qū)使用,inode怎么確定分組呢?比如inode有100萬個(gè),那么可能把10萬個(gè)inode分配給第一個(gè)分組,那么只要0到10萬減一的inode就在第一個(gè)分組
- 有沒有可能,一個(gè)組的數(shù)據(jù)塊沒用完,inode沒了,或者inode沒用完,data blocks用完了?
有可能,比如在分區(qū)中創(chuàng)建大量的空文件,那不就是在消耗inode么。建造一個(gè)文件,瘋狂的往該文件塞大量的數(shù)據(jù),不就是在消耗data blocks么。
操作系統(tǒng)啟動(dòng)時(shí),預(yù)加載除了data blocks的其它內(nèi)容。
軟硬鏈接
1.制造軟硬鏈接,對(duì)比差別
軟鏈接文件的名字應(yīng)該是my_soft。
軟鏈接是一個(gè)獨(dú)立的鏈接文件,有自己的inode number,必有自己的inode屬性和內(nèi)容,其中內(nèi)容放的是自己所指向文件的路徑,類似windows的快捷方式。
硬鏈接是和目標(biāo)文件共用一個(gè)inode number,意味著硬鏈接一定和目標(biāo)文件使用同一個(gè)inode的,硬鏈接沒有獨(dú)立的inode。那硬鏈接干了什么?建立了新的文件名和老的inode的映射關(guān)系。
硬鏈接,采用引用計(jì)數(shù),當(dāng)引用計(jì)數(shù)為0時(shí),才真正刪除文件,不然都是引用計(jì)數(shù)減一。
unlink my_hard 刪除硬鏈接,unlink可用來刪除普通文件。
2.軟硬鏈接的使用場景
在ls查詢中,權(quán)限后面跟的數(shù)字就是引用計(jì)數(shù),硬鏈接某個(gè)文件以后,該文件的這個(gè)數(shù)字會(huì)增加。
3.補(bǔ)充知識(shí)
在創(chuàng)建一個(gè)新的目錄的時(shí)候引用計(jì)數(shù)是2,是因?yàn)樵瓉砦募cinode的映射關(guān)系和.文件名和inode的映射關(guān)系。
ls -di //-d 將文件像目錄一樣顯示 -i 輸出文件索引,也就是inode
不能給目錄建立硬鏈接,為什么?
因?yàn)槿菀自斐森h(huán)路路徑問題。
假如我從根目錄開始,進(jìn)行深度優(yōu)先遍歷查找,當(dāng)查找到/home/zrb/107/test的時(shí)候,再往link_hard目錄查找的時(shí)候,又因?yàn)閘ink_hard是test的硬鏈接,所以本質(zhì)是又回到了test目錄,又再往link_hard的目錄查找,導(dǎo)致了深度優(yōu)先遍歷一直卡在了這條查找路徑上。
上級(jí)目錄…本質(zhì)也是一種環(huán)路路徑問題,/home/zrb/107/test/. . 注意. .是在下一個(gè)目錄里面的,表示當(dāng)前目錄的,所以還是跟上面的情況是一樣的,深度優(yōu)先遍歷的時(shí)候從test目錄進(jìn)去. .目錄,但由于. .目錄是test目錄的硬鏈接,所以本質(zhì)又是回到了test目錄,繼續(xù)往. .目錄遍歷,導(dǎo)致一直卡在了這條搜索路徑,但是,操作系統(tǒng)會(huì)對(duì)遇到. .進(jìn)行特殊處理,所以才不會(huì)死循環(huán)查找。
文件的三種時(shí)間
Access 最后訪問時(shí)間
Modify 文件內(nèi)容最后修改時(shí)間
Change 屬性最后修改時(shí)間
增刪查改中,查的頻率是最高的,如果高頻次的對(duì)文件最后訪問時(shí)間做修改的話,一定意味著高頻次的將修改后的時(shí)間刷新到磁盤外設(shè)中,將提高系統(tǒng)刷新的IO頻次,進(jìn)而導(dǎo)致效率降低,所以在最新的內(nèi)核中把查看的時(shí)間修改策略改變了,也就是不一定每次查看都要修改文件最后訪問時(shí)間,可能每隔上三五次刷新一層,不同的操作系統(tǒng)的刷新策略不同,進(jìn)而減少內(nèi)存和磁盤IO交涉的成本,修改時(shí)間每次都要刷新,訪問數(shù)據(jù)不修改文件內(nèi)容和屬性,即使丟失了,也沒有關(guān)系,但是修改時(shí)間丟失了,就有問題了。
動(dòng)態(tài)庫和靜態(tài)庫
靜態(tài)庫(.a):程序在編譯鏈接的時(shí)候把庫的代碼鏈接到可執(zhí)行文件中。程序運(yùn)行的時(shí)候?qū)⒉辉傩枰o態(tài)庫
動(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ī)器碼
在可執(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)存和磁盤空間。
頭文件和庫的關(guān)系
1.系統(tǒng)已經(jīng)預(yù)裝了c/c++的頭文件和庫文件,頭文件提高方法說明,庫提高方法的實(shí)現(xiàn),頭和庫是有對(duì)應(yīng)關(guān)系的,是要組合在一起使用的。
2.頭文件是在預(yù)處理階段引入的,鏈接的本質(zhì)是鏈接庫。
理解現(xiàn)象:
a.在vs2019、vs2022下安裝開發(fā)環(huán)境——安裝編輯器軟件、安裝要開發(fā)的語言配套的庫和頭文件。
b.我們?cè)谑褂镁庉嬈鳎紩?huì)有語法的自動(dòng)提醒功能,需要先包含頭文件的。語法提醒的本質(zhì):編輯器會(huì)主動(dòng)的將用戶輸入的內(nèi)容,不斷的在被包含的頭文件中進(jìn)行搜索,自動(dòng)提醒功能是依賴頭文件的。
c.我們?cè)趯懘a的時(shí)候,我們的環(huán)境怎么知道我們的代碼有哪些地方有語法錯(cuò)誤,哪些地方定義變量有問題?編輯器有命令行模式和其他自動(dòng)化模式幫我們不斷的進(jìn)行語法檢查。
為什么要有庫
提高開發(fā)效率——可以選擇源代碼、或者編譯成二進(jìn)制代碼給別人。
寫一個(gè)庫
當(dāng)我們寫了一個(gè)庫,要將庫引入我們的項(xiàng)目,必須讓編輯器找到頭文件+庫文件。
在下面的解釋中,全部按照自己實(shí)現(xiàn)了代碼,如何給別人使用的背景下來進(jìn)行操作。
目錄myself就是代表自己,目錄otherperson就是代表其他人
我在目錄myself實(shí)現(xiàn)了幾個(gè)函數(shù),如下
myadd.h文件
#ifndef __ADD_H__
#define __ADD_H__
int add(int a,int b);
#endif
myadd.c文件
#include "myadd.h"
int add(int a,int b)
{
return a + b;
}
mysub.h
#ifndef __SUB_H__
#define __SUB_H__
int sub(int a,int b);
#endif
mysub.c
#include "mysub.h"
int sub(int a,int b)
{
return a - b;
}
接下來就是如何將myself目錄下的文件給otherperson使用。
直接拷貝頭文件和源代碼給別人
此時(shí),其他人便可以調(diào)用函數(shù)的方式去實(shí)現(xiàn)。
#include <stdio.h>
#include "myadd.h"
#include "mysub.h"
int main()
{
printf("%d\n",add(10,15));
printf("%d\n",sub(20,15));
}
但是,一般來說,并不會(huì)直接把源代碼拷貝給其他人,保障知識(shí)產(chǎn)權(quán)。
將源代碼生成二進(jìn)制文件,拷貝給別人
其他人也可以通過這種方式使用你寫的函數(shù)
生成靜態(tài)庫,拷貝給別人
其他人也可以通過這種方式使用你寫的函數(shù)
生成靜態(tài)庫,壓縮發(fā)給別人
其他人通過解壓,便可以進(jìn)行調(diào)用我寫的函數(shù)了。
將頭文件拷貝到系統(tǒng)搜索頭文件的默認(rèn)搜索位置和將庫拷貝到庫的默認(rèn)搜索路徑下
其他直接表明使用的庫名字,便可以進(jìn)行調(diào)用我寫的函數(shù)了。
生成動(dòng)態(tài)庫,拷貝給別人,別人將庫路徑導(dǎo)入環(huán)境變量當(dāng)中,讓操作系統(tǒng)可以查找,就可以運(yùn)行
別人便可以通過動(dòng)態(tài)庫,調(diào)用我寫的函數(shù)了
生成動(dòng)態(tài)庫,拷貝給別人,別人建立動(dòng)態(tài)庫的軟鏈接,就可以運(yùn)行
防止上面環(huán)境變量影響,可以重啟xshell。
別人便可以通過動(dòng)態(tài)庫,調(diào)用我寫的函數(shù)了
生成動(dòng)態(tài)庫,拷貝給別人,別人將動(dòng)態(tài)庫的路徑拷貝到配置文件里,就可以運(yùn)行
防止上面的軟鏈接影響,可以取消軟鏈接。
/etc/ld.so.conf:記錄了程序加載運(yùn)行期間查找動(dòng)態(tài)鏈接庫時(shí)的路徑。
ld:鏈接
so:動(dòng)態(tài)庫
conf:配置文件
配置文件里面放動(dòng)態(tài)庫的路徑。
別人便可以通過動(dòng)態(tài)庫,調(diào)用我寫的函數(shù)了
第三方庫的使用
- 需要指定頭文件和庫文件
- 如果沒有默認(rèn)安裝到gcc、g++的默認(rèn)搜索路徑下,用戶必須指明對(duì)應(yīng)的選項(xiàng),告知編譯器:
a.頭文件在哪里
b.庫文件在哪里
c.庫文件是誰 - 將我們下載下來的庫和頭文件,拷貝到系統(tǒng)的默認(rèn)搜索路徑下——就是在Linux下安裝庫。那么卸載呢?對(duì)任何軟件,安裝和卸載的本質(zhì)就是拷貝到系統(tǒng)特定的路徑下。
- 如果我們安裝的庫是第三方的(語言,操作系統(tǒng)系統(tǒng)接口)庫,我們要正常使用,即使是已經(jīng)全部安裝到了系統(tǒng)中,gcc/g++必須要用-l指明具體庫的名稱。
理解現(xiàn)象:在Linux安裝的大部分指令,都是需要sudo或者超級(jí)用戶操作的。
動(dòng)態(tài)庫配置
運(yùn)行時(shí),OS是如何查找動(dòng)態(tài)庫的
- 環(huán)境變量:LD_LIBRARY_PATH,每次打開xshell,環(huán)境變量都會(huì)被重新繼承,加入的環(huán)境變量會(huì)生效,所以是臨時(shí)方案。
- 軟鏈接方案
- 配置文件方案
在生成靜態(tài)庫,拷貝給別人的實(shí)驗(yàn)中,我們直接告訴編輯器庫在哪里,就可以運(yùn)行了,但是動(dòng)態(tài)庫這里,需要環(huán)境變量或者軟鏈接或者配置文件呢?
gcc -o myfile test.c -I include -L lib -lmymath
上面那條指令只是告訴了編譯器,沒有告訴OS,而上面三種方案,就是告訴OS,庫在哪里,但是,為什么靜態(tài)庫直接就可以運(yùn)行了呢?是因?yàn)椋o態(tài)庫,鏈接原則:將用戶使用的二進(jìn)制代碼直接拷貝到目標(biāo)可執(zhí)行程序中,但是動(dòng)態(tài)庫不會(huì)。
靜態(tài)庫的理解
靜態(tài)鏈接形成的可執(zhí)行程序,本身就有靜態(tài)庫中方法的實(shí)現(xiàn)(非常占用資源,可執(zhí)行程序體積變大,加載占用內(nèi)存,下載周期變長,占用網(wǎng)絡(luò)資源)
動(dòng)態(tài)庫的理解
將可執(zhí)行程序中的外部符號(hào),替換成為庫中的具體的地址。
假設(shè)程序用到了c庫的printf方法,并且當(dāng)前只有動(dòng)態(tài)庫
代碼1、代碼2、代碼3屬于mymath的代碼。
當(dāng)進(jìn)程開始沿著代碼1、代碼2往下執(zhí)行時(shí),遇到printf,經(jīng)過頁表映射,發(fā)現(xiàn)沒有printf代碼,所以整個(gè)進(jìn)程結(jié)合操作系統(tǒng)當(dāng)中立馬意識(shí)到這個(gè)程序依賴動(dòng)態(tài)庫,所以在當(dāng)前操作系統(tǒng)檢索動(dòng)態(tài)庫,將動(dòng)態(tài)庫中的printf方法load到內(nèi)存中,并且建立映射關(guān)系,映射到共享區(qū)中。
執(zhí)行代碼1、代碼2,正常通過頁表映射,找到物理內(nèi)存中的mymath,執(zhí)行對(duì)應(yīng)的代碼,當(dāng)執(zhí)行到printf時(shí),操作系統(tǒng)就會(huì)檢測(cè)到printf已經(jīng)加載進(jìn)來,并映射成功,那么將從共享區(qū)找到printf的虛擬地址,通過頁表映射,找到物理內(nèi)存存儲(chǔ)的printf方法,運(yùn)行該printf方法,然后再回來運(yùn)行原有的代碼。
在加載printf方法時(shí),操作系統(tǒng)會(huì)以4kb為單位去加載,即操作系統(tǒng)會(huì)將printf函數(shù)上下文代碼也加載進(jìn)來,然后就可以加速其他進(jìn)程或者它自己代碼當(dāng)中還會(huì)用到printf周邊的方法,所以才會(huì)這樣加載。
只要把庫中對(duì)應(yīng)的方法加載到內(nèi)存,映射到進(jìn)程的地址空間之后,我們的代碼執(zhí)行庫中的方法,就依舊還是在自己的地址空間內(nèi)進(jìn)行函數(shù)跳轉(zhuǎn)即可。
當(dāng)加載進(jìn)程B,進(jìn)程B依然使用了printf方法,當(dāng)進(jìn)程B使用printf,操作系統(tǒng)檢測(cè)到已經(jīng)加載了printf方法到物理內(nèi)存,此時(shí),直接將內(nèi)存中的printf方法映射到地址空間即可。其他進(jìn)程如果使用printf方法時(shí),也只是直接將內(nèi)存中的printf方法映射到地址空間即可,共享庫的所有公共方法在內(nèi)存里,最終只要一份就可以了,剩下的要用哪一些,就映射哪一些。
庫可能很大,平時(shí)進(jìn)程不一定會(huì)使用庫里面所有的方法,所以說把庫加載到內(nèi)存,不代表同時(shí)可以把庫全部加載到內(nèi)存里,需要哪一個(gè),就加載哪一個(gè),取決于操作系統(tǒng)的策略。
靜態(tài)庫和動(dòng)態(tài)庫加載進(jìn)地址空間的地址規(guī)律
在動(dòng)態(tài)庫加載中,不同的進(jìn)程,運(yùn)行程度是不同的,需要使用的第三方庫是不同的,注定了,每一個(gè)進(jìn)程的共享地址空間中的空閑位置是不確定的。如果采用固定地址映射,比如printf方法在進(jìn)程A映射的地址是0x11223344,但是在進(jìn)程B,0x11223344的地址空間已經(jīng)被用了,那么就沒有法映射0x11223344了。
在程序編譯鏈接形成可執(zhí)行程序的時(shí)候,可執(zhí)行程序內(nèi)部有沒有地址?有。進(jìn)程地址空間就已經(jīng)提到了。
絕對(duì)編址:例如,我在操場的50米左右。
相對(duì)編址:我在操場的樹的左邊大約20米。
當(dāng)一個(gè)庫真正被映射進(jìn)地址空間的時(shí)候,它的起始地址才能真正確定。
調(diào)用printf的時(shí)候,只需要知道調(diào)用哪一個(gè)庫
如:這里調(diào)用lib.so庫
該庫的真正起始地址+123就可以找到printf方法
該庫的真正起始地址是什么?就是映射到進(jìn)程地址空間的起始地址。操作系統(tǒng)會(huì)記錄下來。
動(dòng)態(tài)庫,在進(jìn)程的地址空間中,隨便加載,與我們加載到進(jìn)程地址空間的什么位置,毫無關(guān)系了。
與位置無關(guān)碼:動(dòng)態(tài)庫中的地址都是偏移量。
好處:如庫有1萬個(gè)函數(shù),而這份代碼只用到其中100個(gè)方法,可以實(shí)現(xiàn)局部性加載,需要哪些,加載哪些。
動(dòng)態(tài)庫和靜態(tài)庫都存在,默認(rèn)動(dòng)態(tài)鏈接
在前面的操作中,已經(jīng)存在一些測(cè)試文件了。
上面可以驗(yàn)證,動(dòng)態(tài)庫和靜態(tài)庫都存在,默認(rèn)動(dòng)態(tài)鏈接。
那么如何強(qiáng)制靜態(tài)鏈接呢?文章來源:http://www.zghlxwxcb.cn/news/detail-725068.html
如果不提供動(dòng)態(tài)庫,只提供靜態(tài)庫,那么程序在進(jìn)行動(dòng)態(tài)鏈接的時(shí)候(沒有加-static),沒辦法,只能將庫以靜態(tài)鏈接的方式進(jìn)行鏈接,但是程序不僅僅鏈接一個(gè)庫,還要鏈接C庫,所以其他庫依舊動(dòng)態(tài)鏈接,所以在一個(gè)程序中,鏈接并非只有一種。文章來源地址http://www.zghlxwxcb.cn/news/detail-725068.html
到了這里,關(guān)于系統(tǒng)文件IO、文件描述符fd、重定向、文件系統(tǒng)、動(dòng)態(tài)庫和靜態(tài)庫的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!