首頁 > 軟體

Linux操作檔案的底層系統呼叫,探究父子程序是否可以共用檔案問題

2023-03-23 22:06:31

linux作業系統奉行一切皆檔案的理念,所有檔案裝置幾乎都可以用一套系統呼叫即open()/close()/write()/read()等來操作。系統呼叫和C庫呼叫操作檔案類似。Linux自帶的man手冊是最權威的。通過檢視man手冊來檢視系統呼叫用法。

代號 —— 代表的含義

  • 1 —— 使用者在shell環境下可操作/可執行的命令
  • 2 —— 系統核心可呼叫的函數與工具
  • 3 —— 一些常用的函數與函數庫,大部分C的函數庫
  • 4 —— 裝置檔案的說明,通常是在 /dev下的裝置
  • 5 —— 組態檔或某些檔案的格式
  • 6 —— 遊戲
  • 7 —— 管理與協定等,例如Linux檔案系統、網路協定等
  • 8 —— 系統管理員可用的命令
  • 9 —— 與Kernel有關的檔案

注意,系統的標頭檔案在Linux中一般存放在/usr/include目錄下;下面包含的一些標頭檔案有的帶了sys,其實是include底下的子目錄中的標頭檔案

open()——開啟或者建立一個檔案

返回值型別: int——檔案描述符fd,每開啟一個檔案,就會得到一個檔案描述符,這個檔案描述符是整形的,我們通過檔案描述符進行讀寫操作。

  • 失敗:-1
  • 成功:>= 0,即檔案描述符;
  • mode_t是一個型別別名,實際上就是一個有符號的整數,對open函數而言,僅僅當建立新檔案時才使用第三個引數

flag:開啟標誌

注意: 這些其實都是定義的一些宏,當需要使用到多個引數時,使用按位元或“ | ”構成多個flag引數

也可跟隨下面的方式一起使用:

其他不一一介紹,需要使用時自查。

write()

返回值

  • 若成功為已經寫入的位元組數;
  • 若出錯為-1;

注意:計劃寫入的位元組數和函數的返回值不相等時,表示寫入出現了錯誤,可以用來檢驗寫入是否成功;

引數:

  • fd:寫入檔案的檔案描述符;
  • buf:存放待寫資料的快取;
  • count:要求寫入一次資料的位元組數;

注意:

對於普通檔案,寫操作從檔案的當前位移量處開始,若如果在開啟該檔案時,指定了O_APPEND選擇項,則在每次寫操作之前,將檔案位移量設定在檔案的當前結尾處。在一次成功寫之後,該檔案位移量增加實際寫的位元組數。

read()

返回值 :讀到的位元組數

  • 若已到檔案尾為0;若出錯為-1;

引數

  • fd:讀取檔案的檔案描述符;
  • buf:存放讀取資料的快取;
  • count:要求讀取一次資料的位元組數;注意返回值是實際讀到的位元組數,二者並不相同;

注意:讀操作從檔案的當前位移量開始,在成功返回之前,該位移量增加實際讀得的位元組數(這個位移量是可以自己設定的);

close()

注意:當一個程序終止時,它所開啟的檔案都由核心自動關閉。

注:這些不帶快取的函數都是核心提供的系統呼叫;這正是和我們在C語言中學到的那些IO操作不同的地方,他們不是標準C的組成部分,但是POSIX的組成部分。

標準C對檔案操作時都是通過對FILE的結構體指標進行操作的,而這裡使用的是檔案描述符。

檔案描述符的範圍是0——OPEN MAX,早期的Unix採用的上限為19(即允許每個程序開啟20個檔案),現在很多系統將即增加到63,Linux為1024,具體多少可以在<unistd.h>的標頭檔案中查詢。

檔案描述符與檔案指標

  • FILE *fdopen(int fd,const char *mode),將檔案描述符轉為檔案指標;
  • int fileno(FILE *stream),將檔案指標轉換為檔案描述符;

lseek函數

功能: 定位一個已開啟的檔案

off_t lseek(int fd,off_t offset,int whence);
  • fd:已經開啟的檔案描述符;
  • offset:位移量;
  • whence:定位的位置,即基準點
  • SEEK_SET:將該檔案的位移量設定為距檔案開始處offset個位元組;
  • SEEK_CUR:將該檔案的位移量設定為其當前值加offset,offset可正可負;
  • SEEK_END:將該檔案的位移量設定為檔案長度加offset,offset可正可負(此時若為正值,就涉及到空洞檔案了,請看下面的講解);
  • 返回值:**若成功則返回新的檔案位移量(絕對位移量)**若出錯為-1;定位到檔案尾部時,可以返回檔案的大小;
  • lseek函數也可以用來確定所涉及的檔案是否可以設定位移量,如果檔案描述符所參照的是一個管道或者FIFO,則lseek返回-1,並將errno設定為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個位元組處
	if(lseek(fd,10L;SEERK_END) < 0)
	{
		perror("lseek error");
		exit(1);
	}
	//從檔案尾部的10個位元組處再寫入字串
	if(write(fd,buffer,size) != size)
	{
		perror("write error");
		exit(1);
	}
	close(fd);
	return 0;
}

我們可以看到用more命令檢視檔案內容時,發現顯示的內容只有一次寫入的結果,用od

-c命令檢視檔案的ASSCI碼,我們會發現在兩次內容之間,有10個,這就是空洞,用vim開啟該檔案內容也可以看到,有10個^@符。

注:每個檔案都有一個與其相關聯的“當前檔案偏移量”,它是一個非負整數,用以度量從檔案開始處計算的位元組數。通常讀寫操作都以檔案當前偏移量處開始,並使得偏移量增加所讀或所寫的位元組數。按系統預設,當開啟一個檔案時,除非指定O_APPEND選擇項,否則該檔案位移量被設定為0;

範例:

執行結果如下:

fd = 3的原因是:

系統內部PCB存在一個檔案表,以記錄開啟的檔案,檔案描述符其實就是檔案表的下標

  • 0——FILE* stdin,標準輸入
  • 1——FILE* stdout,標準輸出
  • 3——FILE* stderr,標準錯誤輸出
  • 本程式已經預設開啟了三個檔案,fd排到第四個,所以編號為3

接下來進行檔案讀取

執行結果如下:

應用:利用讀寫對檔案進行復制

首先宣告:我們不區分文字檔案還是二進位制檔案

完成對一個圖片的複製,我們可以使用以下的方案:

  • 先開啟原來的二進位制檔案
  • 開啟一個新的檔案
  • 從原來的二進位制檔案中讀取一部分寫入新檔案
  • 反覆讀寫
  • 直到讀完,寫完就停止【read() == 0作為迴圈停止的條件,讀不到就是讀完了】
  • 完成複製

複製完成

開啟檔案後,fork的子程序能否共用和父程序共用存取同一個檔案?

我們每次開啟檔案以後,會在核心中產生struct file這樣一個結構體,以表示開啟的檔案,記錄著以下資訊:

  • 檔案偏移量(起始從0開始,檔案指標隨著寫入資料進行偏移)
  • 參照計數(幾個程序正在使用這個開啟的檔案)
  • inode節點(存放程序的屬性資訊:誰建立了,名字是什麼,在磁碟哪裡儲存。通過這個inode節點,我們才能找到對應的這個具體的檔案)
  • 開啟方式:比如唯讀方式,只寫方式開啟

測試1:先開啟檔案再fork

close(fd)寫在最外側,父子程序都會關閉,每關閉一次,參照計數減1,直到為0。

執行結果如下:

原因如下:

測試2:先fork再開啟檔案

修改程式碼後,執行結果發生如下變化:

因為父子程序分離後,開啟了各自的檔案,產生了各自的struct file,不再共用檔案偏移量。

在實際的應用場景中,我們更多地使用父程序開啟的檔案,子程序去存取這種形式。

總結

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com