1. 文件描述符fd
2. 文件的打開,讀文件,寫文件
3. OPEN函數的flag_1
3.1 文件的讀寫權限(O_RDONLY ; O_WRONLY ; O_RDWR )
3.2 文件打開的內容操作(O_TRUNC ; O_APPEND)
3.3 結束進程(exit;_exit;Exit)
man 2 是系統調用 ; man 3 是庫函數
4. OPEN函數的flag_2
4.1 文件“創建打開”方式O_CREAT,不存在則創建(O_CREAT | O_EXCL)
4.2 阻塞與非阻塞(O_NOBLOCK)
一般是指設備,
4.3 O_SYNC
無O_SYNC時,write將內容寫入緩衝區即可返回;有O_SYNC時,write阻塞等待底層完成寫入才返回。
5. 文件讀寫的一些細節
5.1 errno和perror
errno=errnumber,linux的錯誤代碼,表示函數哪裏錯了;errno是由OS來維護的一個全局變量,OS內部告訴上層調用者發生了一個什麼樣的錯誤;例如(-37);
注意:不是所有函數錯誤時都會返回errno,需要通過man查看“return value”的描述中是否包含“errno”
perror=print errorno,是一個函數
5.2 read和write的count
count表示想要讀寫的字節數,而返回值是實際完成讀寫的字節數。
count和阻塞+非阻塞結合起來:如果一個函數是阻塞式的,起始要讀取30個字節,結果只有20個字節可讀時函數就會被阻塞住,等待餘下的10個可讀。
當讀取一個2MB的文件時,不可能把count設置成2*1024*1024,則需要把count設置成一個合適的值(2048或4096),然後通過多次讀取來實現文件的完整讀取。
5.3 文件IO效率和標準IO
文件IO是指open,close,read,write,等API函數構成的一套用來讀寫文件的體系,但這套體系的效率並不高。
應用層C語言庫函數提供了一些文件讀寫的函數列表,稱爲標準IO(fopen,fclose,fread,fwrite),這些函數是由“文件IO”封裝而來的,封裝的目的是爲了在應用層添加一個緩衝機制。
標準IO的buf==》文件IO的buf==》內核的buf==》硬盤
6. linux系統如何管理文件
6.1 硬盤中的靜態文件和inode(i節點)
文件一般是以靜態的形式存在硬盤(塊設備)中;塊(多個扇區組成)--扇區(512字節)--字節
硬盤分爲兩個區域:硬盤內容管理表區+數據存儲區;操作系統訪問硬盤的文件時,先查詢硬盤內容管理表(文件信息列表,i節點,包括文件名+扇區號+塊號);
每個文件對應一個iNode結構體,結構體中記錄了文件信息。
快速格式化(快):只刪除了硬盤內容管理表,但是真正的數據存儲區並沒有被刪除,只是刪了inode;
底層格式化(慢):內容管理表和數據存儲區都被刪除了;
6.2內存中被打開的文件和vnode(v節點)_動態文件
一個運行的程序就是一個進程,在程序中打開一個文件就屬於某個進程,每個進程都有一個數據結構用來記錄該進程的所有信息(稱爲“進程信息表”),該進程信息表中有一個指針會指向一個“文件管理表”,文件管理表記錄了當前進程打開的所有文件及其相關信息。文件管理表中用來索引各個打開的文件的index就是文件描述符fd,最終找到的就是一個已經被打開的文件的管理結構體vnode。
一個vnode記錄了一個被打開的文件的各種信息,通過這個文件的fd就可以找到這個文件的vnode進而對這個文件進行各種操作。
6.3文件與流的概念
流stream,文件是字符的合集,文件的讀寫只能一個一個字符的進行。文件讀取或寫入時,就形成了“字符流”。
編程中提到stream的概念,一般都是IO相關的,文件操作時就構成了一個IO流。
7. lseek詳解
7.1 lseek函數介紹
文件指針:當我們對一個文件進行讀寫時,一定操作的是動態文件!動態文件在內存中的形式是以“文件流”的形式存在的。
文件流很長,裏面有很多字節,通過“文件指針”表示;文件指針是vnode中的一個元素,這個文件指針只能通過lseek這個函數進行操作。
打開一個空文件時,文件指針默認指向文件流的起始位置,通過lseek函數可以更改文件指針所指向文件流的位置。
read和write函數都是從當前文件指針處開始操作的,所以當我們用lseek將文件指針移動後,再去使用read或write操作時就是從移動後的位置開始的。
注:之前的例子,一個空文件先write,然後直接read讀取;提示寫成功了,但是讀的內容確實空的。這正是因爲write後文件指針後移的原故。
7.2 使用lseek函數計算文件長度
linux中沒有一個庫函數可以直接返回一個文件的長度,
7.3 用lseek函數構建空洞函數
空洞文件:一個文件的內容有一段是空的;我們打開一個文件後,用lseek
空洞文件的作用:多線程操作文件時特別有用。有時創建一個大文件(視頻文件)時,將文件分成多段,多個線程同時分別操作其中的一段的寫入。
8. 多次打開同一個文件與O_APPEND
8.1 重複打開同一個文件並讀取
一個進程中,兩次打開同一個文件,然後分別進行讀取,看結果會怎麼樣
使用open兩次打開同一個文件時,fd1和fd2所對應的文件指針是不同的2個獨立的指針。因爲文件指針包含於動態文件的文件管理表中,因此可以看出linux系統的進程中不同的fd對應不同的文件管理表。
8.2 重複打開同一個文件並寫入
試驗結果是:分別寫,後邊寫的覆蓋前邊寫的
8.3 加O_APPEND解決覆蓋問題
加上O_APPEND後,重複打開同一個文件並寫入,就變成了可以“接續寫入”
8.4 O_APPEND的實現原理及其原子操作性說明
O_APPEND爲什麼可以將分別寫(覆蓋)變成了接續寫?
其核心在於文件指針:分別寫時2個fd擁有不同的文件指針,並且獨立位移。但是O_APPEND標誌可以讓write和read函數多做一件事就是移動自己文件指針的同時也移動別人的文件指針!
雖然加了O_APPEND,但是fd1和fd2還是擁有各自獨立的文件指針,但是這兩個文件指針相互關聯了起來,一個動了另一個也會動。
原子操作:原子操作一旦開始是不會被打斷的,必須直到操作結束其他代碼才能運行。
每種操作系統中都有一些機制來實現原子操作。
O_APPEND對文件指針的影響就是其對文件的讀寫是“原子的”!
9. 文件共享的實現方式
9.1 什麼是文件共享
什麼是同一個文件:是指靜態文件是同一個,即同一個inode,同一個pathname;
同一個文件被多個獨立的“讀寫體(可以理解爲多個文件描述符)”去同時操作(一個打開後不關閉操作,另一個也打開操作)。
文件共享的意義:實現多線程同時操作一個大文件,以提升文件讀寫效率。
9.2 文件共享的3種實現方式
文件共享的核心就是怎麼弄出來多個文件描述符指向同一個文件;
常見的3種文件共享情形:
第一種是同一個進程中多次使用open打開同一個文件。fd應該不同;
第二種是不同的進程中,分別open打開該文件(由於在不同的進程中fd可能相同也可能不同);
第三種是linux系統提供了dup和dup2兩個API來讓進程複製文件的描述符。
9.3 再論文件描述符
文件描述符的本質是一個數字,這個數字的本質是進程表中文件描述表的一個表項,進程通過文件描述符作爲index去索引查表得到文件表指針,再間接訪問得到這個文件對應的文件表。
文件描述符是操作系統按照一定規律自動分配的;
操作系統規定,fd依次從0開始增加,fd也是有最大限制的;
在早期的linux版本(0.1)中fd的最大值是20,表示一個進程最多允許打開20個文件;
linux中的“文件描述符表”是一個數組(不是鏈表),fd是index,文件表指針是value;
當我們去open時,內核會從文件描述符表中挑選一個未被“佔用”的最小的一個fd返回給我們;文件描述符是循環使用的,被釋放掉後可以繼續使用。
fd中的0,1,2已經被操作系統佔用了,用戶程序 能夠獲得的最小的fd就是3;
fd0,fd1,fd2對應的三個文件分別是stdin,stdout,stderr,也就是標準輸入,標準輸出,標準錯誤;
標準輸入一般對應的是鍵盤,fd0對應的一般是鍵盤;fd1一般對應的是顯示器;
printf函數默認輸出到stdout上了,stdio中還有一個函數叫做fprintf,這個函數就可以指定輸出到哪個文件描述符中。
10. 文件描述符的複製1
10.1 dup函數和dup2函數
10.2 使用dup進行文件描述符複製
dup系統調用對fd進行復制,會返回一個新的文件描述符(比如原來的fd=3,返回的就是4);
dup系統調用有一個特點,就是自己不能指定複製後得到的fd的數字是多少,而是由操作系統內部自動分配的,分配的原則遵守fd分配的原則。
dup返回的fd和之前原來的fd,都指向之前原來的fd所指向的打開的動態文件,操作這兩個fd實際操作的都是之前原來的fd打開的文件,以此構成了“文件共享”!
通過dup複製得到的fd和原來的fd同時向一個文件寫入時,結果是“分別寫”還是“接續寫”呢???
10.3 使用dup的缺陷分析
dup不能指定複製回來得到的fd的數值,dup2系統調用可以解決這個問題;
之前說過fd0,fd1,fd2分別被標準輸入(fd1),標準輸出(fd2),標準錯誤(fd3)佔用;
如果使用close(1)關閉標準輸出,關閉後printf就無法輸出內容到標準輸出了,但是我們可以使用dup重新分配fd1,這是就把舊的fd所打開的文件和這個fd1標準輸出通道綁定在了一起,這稱爲“標準輸出的重定位”!
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FILENAME "1.txt"
int main(void)
{
int fd1=-1,fd2=-1;
fd1=open(FILENAME,O_RDWR|O_CREAT|O_TRUNC,0644);
if(fd1<1)
{
perror("open");
return -1;
}
printf("fd1=%d.\n",fd1);
close(1); //1就是標註輸出stdout,關閉了標準輸出!
//複製文件描述符
fd2=dup(fd1); //因爲close(1),所以這裏fd2一定等於1
//這句話就是把1.txt文件和標準輸出綁定起來了,所以以後輸出到標準輸出的信息可以在1.txt中查看
printf("fd2=%d.\n",fd2);
printf("可以看出我們配合使用close和dup進行文件的重定位操作!\n");
close(fd1);
return -1;
}
11. 文件描述符的複製2
11.1 使用dup2進行文件描述符的複製
dup2和dup作用都是根據原來的文件描述符複製出一個新的文件描述符,但是dup2允許用戶指定新的文件描述符的數字;
使用方法,看man手冊原型即可。
11.2 dup2共享文件交叉寫入測試
dup2複製的文件描述符和原來的文件描述符,雖然數字不一樣,但是這兩個fd指向同一個打開的文件;
而且這兩個文件描述符的write操作是“接續寫”(aabb),不是“分別寫”(bbbb);【實驗驗證】
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FILENAME "1.txt"
int main(void)
{
int fd1=-1,fd2=-1;
fd1=open(FILENAME,O_RDWR|O_CREAT|O_TRUNC,0644);
if(fd1<1)
{
perror("open");
return -1;
}
printf("fd1=%d.\n",fd1);
/*
close(1); //1就是標註輸出stdout,關閉了標準輸出!
//複製文件描述符
fd2=dup(fd1); //因爲close(1),所以這裏fd2一定等於1
//這句話就是把1.txt文件和標準輸出綁定起來了,所以以後輸出到標準輸出的信息可以在1.txt中查看
printf("fd2=%d.\n",fd2);
printf("可以看出我們配合使用close和dup進行文件的重定位操作!\n");
*/
fd2=dup2(fd1,16);
printf("fd2=%d.\n",fd2);
while(1)
{
write(fd1,"aa",2);
sleep(1);
write(fd2,"bb",2); //如果看到的是aabbaabb,則是接續寫;
//如果看到的是bbbbbbbb,則是分別寫!
}
close(fd1);
return -1;
}
11.3 命令行中重定位命令
linux中的shell命令執行後,打印結果都是默認進入到stdout的(本質上是這些命令ls,pwd的源碼都是調用printf進行打印的),所以我們在linux的終端shell中直接看到命令執行的結果;
linux終端中一個重要的重定位符號“>”,該符號可以將ls,pwd等命令的結果重定位到一個文件中如2.txt;
“>”重定向的實現原理就是,利用open+close+dup實現的,open打開2.txt,然後close(1)關閉標準stdout,然後dup將fd=1與2.txt文件關聯起來即可。
12. fcntl函數的介紹
12.1 fcntl的原型和作用
fcntl函數是一個多功能的文件管理工具,接收2個參數+1個變參。第一個參數是fd表示要操作哪個文件,第二個參數cmd表示要進行哪個命令操作,變參是用來傳遞參數的其配合cmd使用。
cmd的形式類似於F_XXX,不同的cmd的功能不同;不用熟悉全部的cmd的含義,只要弄明白一個案例就可以,其他以此類推,碰到不明白的fcntl的cmd時,查詢man手冊即可。
編程的思想:9分模仿+1分創新,第一次肯定是先看懂別人的代碼然後去模仿。
12.2 fcntl常用的cmd
F_DUPFD這個cmd的作用是複製文件描述符(作用類似與dup和dup2),其功能是從可用的fd數字列表中找一個比arg大或者和arg一樣大的數字作爲oldfd複製出來的newfd;F_DUPFD與dup2類似,但是不同的地方是dup2返回的是指定的newfd否則就會出錯,但是F_DUPFD命令返回的是“大於等於”arg的那個數字。【arg爲變參】
SYNOPSIS
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
@fd:被複制的文件描述符,表示要操作哪個文件
@cmd:表示要進行什麼操作
@arg:配合cmd使用
//fd2=dup2(fd1,16);
fd2=fcntl(fd1,F_DUPFD,6); //arg=6時,返回值是大於等於6的值
printf("fd2=%d.\n",fd2);
12.3 使用fcntl模擬dup2
13. 標準IO庫介紹
13.1 標準IO與文件IO的區別
標準IO是C庫函數,而文件IO是linux系統的API;
C語言庫函數是由API封裝而來的,庫函數內部也是通過調用API來完成操作的;但是庫函數多了一層封裝,因此比API好用些。
庫函數比API的一個優勢是:API在不同的操作系統是不是通用的,但是C庫函數在不同操作系統中作用幾乎是一樣的,所以C庫函數具備可移植性而API不具備可移植性。
從性能和易用性上來看,C庫函數一般要好一些,譬如文件IO是不帶緩存的,但是標準IO帶緩存,標準IO的性能要更高。
13.2 常用標準IO函數介紹
常見的標準IO庫函數有:fopen, fclose, fread, fwrite, fflush;fseek!
13.3 一個簡單的標準IO讀寫文件示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FILENAME "1.txt"
int main()
{
FILE *fp=NULL;
size_t len=-1;
int array[10]={1,2,3,4,5};
char buff[100]={0};
//fp=fopen(FILENAME,"W+");
fp=fopen(FILENAME,"r+");
if(NULL == fp)
{
perror("fopen");
exit(-1);
}
printf("fopen success.fp=%d.\n",fp);
//這裏是寫文件
//len=fwrite("abcde",1,5,fp);
//len=fwrite(array,sizeof(int),sizeof(array)/sizeof(arry[0]),fp);
//在這裏讀文件
memset(buff,0,sizeof(buff));
len=fread(buff,1,10,fp);
printf("len=%d.\n",len);
printf("buff is [%s].\n",buff);
fclose(fp);
return 0;
}