Linux操作文件的底層系統(tǒng)調(diào)用,探究父子進(jìn)程是否可以共享文件問(wèn)題
linux操作系統(tǒng)奉行一切皆文件的理念,所有文件設(shè)備幾乎都可以用一套系統(tǒng)調(diào)用即open()/close()/write()/read()等來(lái)操作。系統(tǒng)調(diào)用和C庫(kù)調(diào)用操作文件類似。Linux自帶的man手冊(cè)是最權(quán)威的。通過(guò)查看man手冊(cè)來(lái)查看系統(tǒng)調(diào)用用法。
代號(hào) —— 代表的含義
- 1 —— 用戶在shell環(huán)境下可操作/可執(zhí)行的命令
- 2 —— 系統(tǒng)內(nèi)核可調(diào)用的函數(shù)與工具
- 3 —— 一些常用的函數(shù)與函數(shù)庫(kù),大部分C的函數(shù)庫(kù)
- 4 —— 設(shè)備文件的說(shuō)明,通常是在 /dev下的設(shè)備
- 5 —— 配置文件或某些文件的格式
- 6 —— 游戲
- 7 —— 管理與協(xié)議等,例如Linux文件系統(tǒng)、網(wǎng)絡(luò)協(xié)議等
- 8 —— 系統(tǒng)管理員可用的命令
- 9 —— 與Kernel有關(guān)的文件
注意,系統(tǒng)的頭文件在Linux中一般存放在/usr/include
目錄下;下面包含的一些頭文件有的帶了sys,其實(shí)是include底下的子目錄中的頭文件
open()——打開或者創(chuàng)建一個(gè)文件
返回值類型: int——文件描述符fd,每打開一個(gè)文件,就會(huì)得到一個(gè)文件描述符,這個(gè)文件描述符是整形的,我們通過(guò)文件描述符進(jìn)行讀寫操作。
- 失?。?1
- 成功:>= 0,即文件描述符;
- mode_t是一個(gè)類型別名,實(shí)際上就是一個(gè)有符號(hào)的整數(shù),對(duì)open函數(shù)而言,僅僅當(dāng)創(chuàng)建新文件時(shí)才使用第三個(gè)參數(shù)
flag:打開標(biāo)志
注意: 這些其實(shí)都是定義的一些宏,當(dāng)需要使用到多個(gè)參數(shù)時(shí),使用按位或“ |
”構(gòu)成多個(gè)flag參數(shù)
也可跟隨下面的方式一起使用:
其他不一一介紹,需要使用時(shí)自查。
write()
返回值:
- 若成功為已經(jīng)寫入的字節(jié)數(shù);
- 若出錯(cuò)為-1;
注意:計(jì)劃寫入的字節(jié)數(shù)和函數(shù)的返回值不相等時(shí),表示寫入出現(xiàn)了錯(cuò)誤,可以用來(lái)檢驗(yàn)寫入是否成功;
參數(shù):
fd
:寫入文件的文件描述符;buf
:存放待寫數(shù)據(jù)的緩存;count
:要求寫入一次數(shù)據(jù)的字節(jié)數(shù);
注意:
對(duì)于普通文件,寫操作從文件的當(dāng)前位移量處開始,若如果在打開該文件時(shí),指定了O_APPEND選擇項(xiàng),則在每次寫操作之前,將文件位移量設(shè)置在文件的當(dāng)前結(jié)尾處。在一次成功寫之后,該文件位移量增加實(shí)際寫的字節(jié)數(shù)。
read()
返回值 :讀到的字節(jié)數(shù)
- 若已到文件尾為0;若出錯(cuò)為-1;
參數(shù)
fd
:讀取文件的文件描述符;buf
:存放讀取數(shù)據(jù)的緩存;count
:要求讀取一次數(shù)據(jù)的字節(jié)數(shù);注意返回值是實(shí)際讀到的字節(jié)數(shù),二者并不相同;
注意:讀操作從文件的當(dāng)前位移量開始,在成功返回之前,該位移量增加實(shí)際讀得的字節(jié)數(shù)(這個(gè)位移量是可以自己設(shè)置的);
close()
注意:當(dāng)一個(gè)進(jìn)程終止時(shí),它所打開的文件都由內(nèi)核自動(dòng)關(guān)閉。
注:這些不帶緩存的函數(shù)都是內(nèi)核提供的系統(tǒng)調(diào)用;這正是和我們?cè)贑語(yǔ)言中學(xué)到的那些IO操作不同的地方,他們不是標(biāo)準(zhǔn)C的組成部分,但是POSIX的組成部分。
標(biāo)準(zhǔn)C對(duì)文件操作時(shí)都是通過(guò)對(duì)FILE的結(jié)構(gòu)體指針進(jìn)行操作的,而這里使用的是文件描述符。
文件描述符的范圍是0——OPEN MAX,早期的Unix采用的上限為19(即允許每個(gè)進(jìn)程打開20個(gè)文件),現(xiàn)在很多系統(tǒng)將即增加到63,Linux為1024,具體多少可以在<unistd.h>的頭文件中查找。
文件描述符與文件指針
- FILE *fdopen(int fd,const char *mode),將文件描述符轉(zhuǎn)為文件指針;
- int fileno(FILE *stream),將文件指針轉(zhuǎn)換為文件描述符;
lseek函數(shù)
功能: 定位一個(gè)已打開的文件
off_t lseek(int fd,off_t offset,int whence);
fd
:已經(jīng)打開的文件描述符;offset
:位移量;whence
:定位的位置,即基準(zhǔn)點(diǎn)SEEK_SET
:將該文件的位移量設(shè)置為距文件開始處offset個(gè)字節(jié);SEEK_CUR
:將該文件的位移量設(shè)置為其當(dāng)前值加offset,offset可正可負(fù);SEEK_END
:將該文件的位移量設(shè)置為文件長(zhǎng)度加offset,offset可正可負(fù)(此時(shí)若為正值,就涉及到空洞文件了,請(qǐng)看下面的講解);- 返回值:**若成功則返回新的文件位移量(絕對(duì)位移量)**若出錯(cuò)為-1;定位到文件尾部時(shí),可以返回文件的大小;
- lseek函數(shù)也可以用來(lái)確定所涉及的文件是否可以設(shè)置位移量,如果文件描述符所引用的是一個(gè)管道或者FIFO,則lseek返回-1,并將errno設(shè)置為EPLPE;
空洞文件示例:
#include<stdio.h> #include<fcntl.h> #include<string.h> #include<stdlib.h> #include<unistd.h> #include<errno.h> //生成空洞文件 char *buffer = "0123456789"; int main(int argc,char *argv[]) { if(argc < 2) { fprintf(stderr,"-usage:%s [file]\n",argv[0]); exit(1); } int fd = open(argv[1],O_WRONLY | O_CREATE | O_TRUNC,0777); if(fd < 0) { perror("open error"); exit(1); } size_t size = strlen(buffer) * sizeof(char); //將字符串寫入到空洞文件中 if(write(fd,buffer,size) != size) { perror("write error"); exit(1); } //定位到文件尾部的10個(gè)字節(jié)處 if(lseek(fd,10L;SEERK_END) < 0) { perror("lseek error"); exit(1); } //從文件尾部的10個(gè)字節(jié)處再寫入字符串 if(write(fd,buffer,size) != size) { perror("write error"); exit(1); } close(fd); return 0; }
我們可以看到用more命令查看文件內(nèi)容時(shí),發(fā)現(xiàn)顯示的內(nèi)容只有一次寫入的結(jié)果,用od
-c命令查看文件的ASSCI碼,我們會(huì)發(fā)現(xiàn)在兩次內(nèi)容之間,有10個(gè)\0,這就是空洞,用vim打開該文件內(nèi)容也可以看到,有10個(gè)^@符。
注:每個(gè)文件都有一個(gè)與其相關(guān)聯(lián)的“當(dāng)前文件偏移量”,它是一個(gè)非負(fù)整數(shù),用以度量從文件開始處計(jì)算的字節(jié)數(shù)。通常讀寫操作都以文件當(dāng)前偏移量處開始,并使得偏移量增加所讀或所寫的字節(jié)數(shù)。按系統(tǒng)默認(rèn),當(dāng)打開一個(gè)文件時(shí),除非指定O_APPEND選擇項(xiàng),否則該文件位移量被設(shè)置為0;
示例:
運(yùn)行結(jié)果如下:
fd = 3的原因是:
系統(tǒng)內(nèi)部PCB存在一個(gè)文件表,以記錄打開的文件,文件描述符其實(shí)就是文件表的下標(biāo)
- 0——FILE* stdin,標(biāo)準(zhǔn)輸入
- 1——FILE* stdout,標(biāo)準(zhǔn)輸出
- 3——FILE* stderr,標(biāo)準(zhǔn)錯(cuò)誤輸出
- 本程序已經(jīng)默認(rèn)打開了三個(gè)文件,fd排到第四個(gè),所以編號(hào)為3
接下來(lái)進(jìn)行文件讀取
運(yùn)行結(jié)果如下:
應(yīng)用:利用讀寫對(duì)文件進(jìn)行復(fù)制
首先聲明:我們不區(qū)分文本文件還是二進(jìn)制文件
完成對(duì)一個(gè)圖片的復(fù)制,我們可以使用以下的方案:
- 先打開原來(lái)的二進(jìn)制文件
- 打開一個(gè)新的文件
- 從原來(lái)的二進(jìn)制文件中讀取一部分寫入新文件
- 反復(fù)讀寫
- 直到讀完,寫完就停止【read() == 0作為循環(huán)停止的條件,讀不到就是讀完了】
- 完成復(fù)制
復(fù)制完成
打開文件后,fork的子進(jìn)程能否共享和父進(jìn)程共享訪問(wèn)同一個(gè)文件?
我們每次打開文件以后,會(huì)在內(nèi)核中產(chǎn)生struct file這樣一個(gè)結(jié)構(gòu)體,以表示打開的文件,記錄著以下信息:
- 文件偏移量(起始從0開始,文件指針隨著寫入數(shù)據(jù)進(jìn)行偏移)
- 引用計(jì)數(shù)(幾個(gè)進(jìn)程正在使用這個(gè)打開的文件)
- inode節(jié)點(diǎn)(存放進(jìn)程的屬性信息:誰(shuí)創(chuàng)建了,名字是什么,在磁盤哪里存儲(chǔ)。通過(guò)這個(gè)inode節(jié)點(diǎn),我們才能找到對(duì)應(yīng)的這個(gè)具體的文件)
- 打開方式:比如只讀方式,只寫方式打開
測(cè)試1:先打開文件再fork
close(fd)寫在最外側(cè),父子進(jìn)程都會(huì)關(guān)閉,每關(guān)閉一次,引用計(jì)數(shù)減1,直到為0。
運(yùn)行結(jié)果如下:
原因如下:
測(cè)試2:先f(wàn)ork再打開文件
修改代碼后,運(yùn)行結(jié)果發(fā)生如下變化:
因?yàn)楦缸舆M(jìn)程分離后,打開了各自的文件,產(chǎn)生了各自的struct file,不再共享文件偏移量。
在實(shí)際的應(yīng)用場(chǎng)景中,我們更多地使用父進(jìn)程打開的文件,子進(jìn)程去訪問(wèn)這種形式。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Mac OS X中設(shè)置VIM語(yǔ)法高亮的方法
大家應(yīng)該都知道在默認(rèn)情況下,使用vim打開文本文件都是灰蒙蒙的一片,當(dāng)我們要在其中查詢某個(gè)字符的時(shí)候也看的不清楚。但是,其實(shí)是有辦法解決這種困境。下面這篇文章就給給大家分享了在Mac OS X中設(shè)置VIM語(yǔ)法高亮的方法,有需要的朋友們可以參考借鑒,下面來(lái)一起看看。2016-11-11記一次linux服務(wù)器入侵應(yīng)急響應(yīng)(小結(jié))
這篇文章主要介紹了記一次linux服務(wù)器入侵應(yīng)急響應(yīng),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-08-08centos7.2.1511安裝jdk1.8.0_151及mysql5.6.38的方法
這篇文章主要介紹了centos7.2.1511安裝jdk1.8.0_151及mysql5.6.38的方法,較為詳細(xì)的講述了centos7.2.1511安裝jdk1.8.0_151及mysql5.6.38的具體步驟與相關(guān)設(shè)置技巧,需要的朋友可以參考下2018-01-01Linux系統(tǒng)下移植busybox中mkfs.vfat命令
為了延長(zhǎng)磁盤壽命來(lái)存儲(chǔ)音視頻文件,打發(fā)在格式化磁盤過(guò)程中將簇的大小設(shè)置大點(diǎn)。這接下來(lái)通過(guò)本文給大家介紹移植busybox中mkfs.vfat命令,需要的朋友可以參考下2019-07-07ubuntu中編寫shell腳本開機(jī)自動(dòng)啟動(dòng)(推薦)
這篇文章主要介紹了ubuntu中編寫shell腳本開機(jī)自動(dòng)啟動(dòng),本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12Linux下用netstat查看網(wǎng)絡(luò)狀態(tài)、端口狀態(tài)
這篇文章主要介紹了Linux下用netstat查看網(wǎng)絡(luò)狀態(tài)、端口狀態(tài)的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-12-12