1. 程式人生 > >Linux系統檔案I/O程式設計(一)---open()等基本函式

Linux系統檔案I/O程式設計(一)---open()等基本函式

Linux檔案I/O系統概述

    虛擬檔案系統(VFS)

    Linux系統成功的關鍵因素之一就是具有與其他作業系統和諧共存的能力。Linux系統的檔案系統由兩層結構構建:第一層是虛擬檔案系統(VFS),第二層是各種不同的具體的檔案系統。

    VFS就是把各種具體的檔案系統的公共部分抽取出來,形成一個抽象層,是系統核心的一部分,它位於使用者程式和具體的檔案系統之間。它對使用者提供了標準的檔案系統呼叫介面,對具體的檔案系統(如EXT2、FAT32等),它通過一系列的對不同檔案系統公用的函式指標來實際呼叫具體的檔案系統函式,完成實際的各有差異的操作。任何使用檔案系統的程式必須經過這層介面來使用它

。通過這樣的方式,VFS就對使用者遮蔽了底層檔案系統的實現細節和差異。

    VFS的作用:①對具體的檔案系統的資料結構進行抽象,以一種統一的資料結構進行管理;②接受使用者層的系統呼叫,如open()、read()、write()、stat()、link()等;③支援多種具體檔案系統之間的相互訪問,接受核心其他子系統的操作請求,例如,記憶體管理和程序排程。

    VFS在linux系統中的位置如下圖1所示:

   

    通過命令:cat /proc/filesystems 可以檢視系統中支援哪些檔案系統

   

    第一列說明檔案系統是否需要掛接在一個塊裝置上。nodev表明後面的檔案系統不需要掛接在塊裝置上。

    第二列是核心支援的檔案系統。

Linu中的檔案及檔案描述符

    與windows不同,Linux作業系統都是基於檔案概念的(這個很很重要啊),檔案是以字元序列構成的資訊載體。根據這一點,可以把I/O裝置當做檔案來處理。因此,與磁碟上的普通檔案進行互動所用的同一系統呼叫可以直接用於I/O裝置。這樣大大簡化了系統對不同裝置的處理,提高了效率。

    Linux中的檔案主要分為4種:普通檔案、目錄檔案、連結檔案和裝置檔案,如下圖:

   

       核心如何區分和引用特定的檔案呢?這裡用到了一個重要的概念-------檔案描述符。

       在Linux中,所有對裝置和檔案的操作都是使用檔案描述符來進行的

。檔案描述符是一個非負的整數,它是一個索引值,並指向在核心中每個程序開啟檔案的記錄表。當開啟一個現存檔案或建立一個新檔案時,核心就向程返回一個檔案描述符;當需要讀寫檔案時,也需要把檔案描述符作為引數傳遞給相應的函式。(咱可以這樣理解,只有當對檔案進行操作時,該檔案才會有檔案描述符,程序沒有用到的檔案統統不給描述符)

      通常,一個程序啟動時,都會開啟3個檔案:標準輸入、標準輸出和標準出錯處理。這3個檔案分別對應描述符為0、1和2(也就是巨集替換 STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO)。

     基於檔案描述符的I/O操作雖然不能直接移植到類Linux以外的系統上(如Windows),但它往往是實現某些I/O操作的唯一途徑,如Linux中底層檔案操作函式(下邊會講)、多路I/O、TC[/IP套接字程式設計介面等、同時,它們也很好地相容Posix標準,因此,可以很方便地移植到任何Posix平臺上。基於檔案描述符的I/O操作是Linux中最常用的操作之一,下面就講講它。

底層檔案I/O操作

    這次主要介紹檔案I/O操作的系統呼叫,主要用到5個函式:open()、read()、write()、lseek()和close()。這些函式的特點不帶快取,直接對檔案(包括裝置)進行讀寫操作。

1、基本檔案操作

函式說明

    ● open()函式用於開啟或建立檔案,在開啟或者建立檔案時可以指定檔案的屬性及使用者的許可權等各種引數。

    ● close()函式用於關閉一個被開啟的檔案。當一個程序終止時,所有被它開啟的檔案都由核心自動關閉,很多程式都使用這一功能而不顯示地關閉一個檔案。

    ● read()函式用於將從指定的檔案中讀出的資料放到快取區中,並返回實際讀入的位元組數。若返回0,則表示沒有資料可讀,即已到達檔案尾。讀操作從檔案的當前指標位置開始。當從裝置檔案中讀出資料時,通常一次最多讀一行。

    ● write()函式用於向開啟的檔案寫資料。寫操作從檔案的當前指標位置開始,對磁碟檔案進行寫操作,若磁碟已滿或者超出該檔案的長度,則write()函式返回失敗。

    ● lseek()函式用於在指定的檔案描述符中將檔案指標定位到相應的位置。每一個已開啟的檔案都有一個讀寫位置,當開啟檔案時,其讀寫位置通常指向檔案開頭;若是以附加的方式開啟檔案(如O_APPEND),則讀寫位置會指向檔案尾。當read()或者write()時,讀寫位置會隨之增加,lseek()便是用來控制該檔案的讀寫位置的。它只能用在可定位(可隨機訪問)檔案操作中。管道、套接字和大部分字元裝置檔案是不可定位的,所以在這些檔案的操作中無法使用lseek()呼叫。

函式格式

    下面我以表格的形式將這5個函式的格式寫出來,接下來再加上我的基礎實驗。

   

     在open()函式中,flag引數可通過 “|” 組合構成,但前3個標誌常量(O_RDONLY、O_WRONLY及O_RDWR)不能相互組合。perms是檔案的存取許可權,既可以用巨集定義表示法,也可以用八進位制表示法。

    

   

      在讀普通檔案時,若讀到要求的位元組數前已到達檔案的尾部,則返回的位元組數會小於希望讀書的位元組數。

     

   在寫普通檔案時,寫操作從檔案的當前指標位置開始。

 

   下面是lseek較特別的使用

    ☆ 欲將讀寫位置移到檔案開頭時:lseek(int fd,0,SEEK_SET)

    ☆ 欲將讀寫位置移到檔案尾時:lseek(int fd,0,SEEK_END)

    ☆ 想要取得目前檔案位置時:lseek(ind fd,0,SEEK_CUR)

    注意:Linux系統不允許lseek()對tty裝置作用,此動作會令lseek()返回ESPIPE。

    另外,其實還有一個檔案建立函式creat(),它的函式原型是int creat(const char *pathname,int perms),它相當於使用下列的呼叫方式呼叫open():

    open(const char *pathname,(O_CREAT|O_WRONLY|O_TRUNC));

基礎實驗1:

    實驗說明:主要是為了演示open()函式的使用方法。首先在自己的目錄下使用命令:vi open.c建立一個檔案,如下圖,我在路徑/home/song/lianxi資料夾下建立的:
   

    然後編寫open.c檔案內容,內容如下:

   

    編輯並儲存open.c後的資料夾所含全部檔案,如下:

   

    使用命令:gcc open.c -o open編譯c檔案,如下:

   

    執行命令:./open 可以看到咱們的實驗成功輸出了:

   

   使用命令:more temp檢視一下temp的檔案,裡邊有咱們使用write()寫的內容:

  

    我將這個檔案內容上傳到了:點此下載,可以自行下載

基礎實驗2

    實驗說明:基本功能是從一個檔案(原始檔)中讀取最後2KB資料並複製到另一個檔案(目標檔案)。在例項中原始檔是以只讀方式開啟的,目標檔案是以只寫方式(可以使讀/寫方式)開啟的。若目標檔案不存在,可以建立並設定許可權的初始值為644,即檔案所有者可讀可寫,檔案所屬組合其他使用者只能讀。

    首先先後使用命令:vi copy_file.c和 vi src.c 在自己的實驗目錄下建立檔案,如下圖

  

   然後再編輯copy_file.c的檔案內容,如下

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

#define Buffer_Size 1024   /*每次讀寫快取大小為1KB,大小不同,執行效率不同*/
#define Src_File_Name  "/home/song/lianxi/src.c"    /*原始檔名,建議使用巨集定義*/
#define Dest_File_Name "/home/song/lianxi/dest.c"      //目標檔名*/
#define Offset     1024*2      //複製的資料大小,這裡為2KB*/

int main()
{
 int src_fd,dest_fd;  /*檔案描述符*/
 unsigned char buff[Buffer_Size];   /*定義用於緩衝資料的陣列*/
 int real_read_len;     /*read()函式實際讀取到的位元組*/
 /*以只讀方式開啟原始檔*/
 src_fd=open(Src_File_Name,O_RDONLY);
 /* 以只寫方式開啟目標檔案,若此檔案不存在則建立該檔案,訪問許可權為644*/
 dest_fd=open(Dest_File_Name,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
 /*如果開啟檔案出錯,則退出程式*/
 if(src_fd<0||dest_fd<0)
 {
  printf("Open file error\n");
  exit(1);   
 } 
 /* 將原始檔的讀/寫指標移到最後2KB的起始位置*/
 lseek(src_fd,-Offset,SEEK_END);
 /* 讀取原始檔的最後2KB資料並寫到目標檔案中,每次讀寫1KB*/
 while((real_read_len=read(src_fd,buff,sizeof(buff)))>0)
 {
  write(dest_fd,buff,real_read_len); 
 }
 /* 關閉檔案,釋放資源*/
 close(dest_fd);
 close(src_fd);
 return 0;
}

     其中src.c的檔案內容你可以自己隨便的放內容,但是要注意檔案內容要大於2KB。

    這兩個檔案我上傳到資源網站,可以自行下載:點此下載

    編輯完之後,使用命令: gcc copy_file.c -o copy_file 編譯檔案,然後執行命令:./copy_file,可以看到自動生成了dest.c檔案

   

    使用命令:more dest.c可以看到檔案的內容

    使用命令:ls -l dest.c可以看到該檔案的大小正好為2KB(2048)