1. 程式人生 > >Unix環境高階程式設計---檔案I/O

Unix環境高階程式設計---檔案I/O

學習了一段時間Unix環境程式設計了,看到程序和執行緒這部分感覺之後,就感覺進步比較緩慢,所以在緩慢的過程中,希望可以多前面的知識進行一定的複習和鞏固,希望以寫的方式,加深對前面知識的理解。從第三章到第五章都是和檔案和I/O相關的。這章總結的是第三章的相關內容。

  • 檔案描述符

在Unix系統中,或者對於更加屬性的Linux系統中,所有的裝置都是用檔案來統一定義的。檔案描述符就是對檔案的一種標識,所有的開啟檔案都通過檔案描述符引用。檔案描述符是一個非負整數。所有對檔案的操作,比如讀寫,都可以通過檔案描述符來對檔案實施操作。

在Unix系統中,或者對於大多數作業系統中,定義了三個常用的檔案描述符。

  • 檔案描述符0與標準輸入相關聯
  • 檔案描述符1與標準輸出相關聯
  • 檔案描述符2與標準錯誤相關聯
  • 在<unistd.h>中,將0,1,2定義了更具有可讀性之的符號常量,STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO
  • 檔案的開啟,建立以及關閉

檔案的開啟、建立和關閉有以下函式原型

#include<fcntl.h>
int open(const char *path, int oflags, mode_t mode)
int openat(int fd, const char *path,int oflags, mode_t mode)

檔案的開啟、建立是在標頭檔案fcntl.h中定義的。其中對

open函式而言引數的含義是:

  • 第一個引數為路徑名
  • 第二個引數為檔案狀態標誌

O_RDONLY或0:可讀

O_WRONLY或1:可寫

O_RDWR或2:可執行

O_APPEND:每次寫時都加到檔案的尾部

O_CREAT:若此檔案不存在則建立它,使用此選項時,需同時說明第三個引數mode,用其說明該新檔案的存取許可權位

O_EXCL:如果同時指定了O_CREAT,而檔案已經存在,則出錯,這可測試一個檔案是否存在,如果不存在則建立此檔案成為一個原子操作

O_TRUNC:如果此檔案存在,而且為只讀或只寫成功開啟,則將其長度截短為0

O_NOCTTY:如果pathname指的是終端裝置,則不將此裝置分配作為此程序的控制終端

O_NONBLOCK:如果pathname指的是一個FIFO,一個塊特殊檔案或一個字元特殊檔案,則此選擇項為此檔案的本次開啟操作和後續的I/O操作設定非阻塞方式

O_SYNC:使每次write都等到物理I/O操作完成

  • 第三個引數一般為檔案許可權,第三個引數一般在建立新檔案才會使用這個引數。即第二個引數選擇為O_CREAT

S_IRWXU,0700:代表該檔案所有者具有可讀,可寫,可指向的許可權

S_IRUSR或S_IREAD,0400:代表該檔案所有者具有可讀取的許可權

S_IWUSR或S_IWRITE,0200:代表該檔案所有者具有可寫入的許可權

S_IXUSR或S_IEXEC,0100:代表該檔案所有者具有可指向的許可權

S_IRWXG,0070:代表該檔案使用者組具有可讀,可寫,可執行的許可權

S_IRGRP,0040:代表該檔案使用者組具有可讀的許可權

S_IWGRP,0020:代表該檔案使用者組具有可寫的許可權

S_IXGRP,0010:代表該檔案使用者組具有可執行的許可權

S_IRWXO,0007:代表其他使用者具有可讀,可寫,可執行的許可權

S_IROTH,0004:代表其他使用者具有可讀的許可權

S_IWOTH,0002:代表其他使用者具有可寫的許可權

S_IXOTH,0001:代表其他使用者具有可執行的許可權

open函式和openat函式的區別在於:

  • path函式指定的是絕對路徑名的時候,fd引數可以被忽略,openat函式相當於open函式
  • path函式指定的是相對路徑名的時候,fd引數指出了相對路徑名在檔案系統中的開始地址,fd引數是通過開啟相對路徑名所在目錄來獲取。
  • path引數指定了相對路徑名,fd引數具有特殊值AT_FDWD,這種情況下,路徑名在當前目錄中獲取,這個時候兩個函式功能類似。

看到這裡的時候,個人本能的是矇蔽的,主要是書上沒有給出具體的例子,加上還沒有接觸第四章內容於是乎就去查閱了相關的資料。比如對於一個檔案,其絕對路徑名是home/effort/桌面/apue.3e/fileio/output.log。其目錄路徑為home/effort/桌面/apue.3e,那麼fd指的就是目錄的路徑,path指代的就是output.log,簡單的使用如下:

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

int main()
{
  DIR *dir;
  int dirfd2;
  int fd;
  int n;

  dir = opendir("../fileio");
  if(NULL == dir)
  {
    perror("open dir error");
    return -1;
  }
  dirfd2 = dirfd(dir);
  if(-1 == dirfd2)
  {
    perror("dirfd error");
    return -1;
  }

  fd = openat(dirfd2,"output.log",O_CREAT|O_RDWR|O_TRUNC);
  if(-1 == fd)
  {
    perror("opeat error");
    return -1;
  }
  n = write(fd,"Hello world!\n",15);
  
  close(fd);
  closedir(dir);

  return 0;

}

執行程式之前,檔案內容如下:


執行之後:


  • 檔案的關閉

檔案的關閉的函式是在標頭檔案unistd.h(很想知道,問啥不在一個統一的標頭檔案裡面),函式原型為:

#include<unistd.h>
int close(fd)
當一個程序終止時,核心會自動關閉它所有開啟的檔案,很多時候都會利用這種功能而不顯式地呼叫close關閉檔案

  • 檔案的讀寫和定位

當開啟檔案後,擁有對檔案的讀寫的許可權時,我們就可以對於檔案進行讀寫了。很多時候,我們還面臨著在檔案不同的位置進行操作的需求,因此還需要對檔案進行定位操作。檔案的定位意思是將使檔案讀寫從當前檔案偏移量處開始。一般開啟檔案時,預設的情況下為0。

所謂的當前檔案偏移量是用於度量從該檔案開始處計算的位元組數。可以呼叫lseek顯式地開啟一個檔案設定偏移量。函式原型為:

#include<unistd.h>
off_t lseek(int fd, off_t offset, int wherence)
其中offset和第三個引數wherence緊密相關,具體關係如下:

wherenceSEEK_SET,則檔案偏移量設定為距當前檔案開始處offset個位元組

wherenceSEEK_CUR,則檔案偏移量設定為距當前值加上offset,offset可負可正

wherenceSEEK_END,則檔案偏移量設定為檔案長度加offset,同樣可負可正

若lseek呼叫成功,則返回新的檔案偏移量。

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main()
{
    int fd;
    fd=open("./output.log",O_RDONLY);
    if(fd<0)
    {printf("can not open file");return -1;}

  off_t offset;
  offset=lseek(fd,0,SEEK_CUR);
  printf("%d\n",offset);

  offset=lseek(fd,0,SEEK_END);
  printf("%d\n",offset);
}

其中output.log的內容為:hello

輸出結果為:



從檔案中讀取資料,需要呼叫read函式,read函式模型為:

#include<unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes)
若read成功,則返回讀到的位元組數,如已到達檔案末尾,則返回0. 讀操作從當前偏移量開始,在成功返回之前,該偏移量將會增加實際讀到的位元組數。

向檔案中寫資料這需要write函式。

#include<unistd.h>
ssize_t write(int fd, const void* buf, size_t nbytes)
若寫成功,則返回實際寫入的位元組數,否則返回-1.所以一般判定寫資料成功,不能單單隻從-1來判斷,而應該從返回實際的寫入位元組數和nbytes比較,相等則表示寫入資料成功。

對於讀寫資料而言,都是從當前檔案偏移量開始的,所以要鎖定任意位置,lseek就可以發揮作用了。

此外還有一個問題,就是對於buf大小的選定。在APUE第三章對此內容進行了詳細的解釋:檔案系統為ext4的磁碟長度為4096位元組。經過測試,系統CPU時間最小為4096左右及以後的位置,因此很多時候,我們都看點BUFFSIZE為4096的原因了。