1. 程式人生 > >UNIX環境高階程式設計(5):檔案I/O(1)

UNIX環境高階程式設計(5):檔案I/O(1)

UNIX系統中的大多數檔案I/O只需要用到5個函式:open、read、write、lseek以及close。本章說明的函式經常稱為“不帶緩衝的I/0”,術語不帶緩衝指的是每個read和write都呼叫核心中的一個系統呼叫。這些不帶緩衝的I/O函式不是ISO C的組成部分,但是它們是POSIX.1和Single UNIX Specification的組成部分。

檔案描述符:

對核心而言,所有開啟的檔案都通過檔案描述符引用。檔案描述符是個非負整數。按照慣例,UNIX系統shell使用檔案描述符0與程序的標準輸入相關聯,檔案描述符1與標準輸出相關聯,檔案描述符2與標準出錯相關聯。這是各種shell以及許多應用程式使用的慣例,而與UNIX核心無關。

在依從POSIX的應用程式中,幻數0、1、2應當替換成符號常量STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,這些常量都定義在<unistd.h>中。

檔案描述符的變化範圍是0-OPEN_MAX。

open函式:

呼叫open函式可以開啟或建立一個檔案:

#include <fcntl.h>

int open(const char *pathname,  int flag,  .../* mode_t mode */);

返回值,若成功則返回檔案描述符,若出錯則返回-1

pathname:是要開啟或建立檔案的名字,flag引數可以說明此函式的多個選項,用下列一個或多個常量進行或運算,構成flag引數。

  • O_RDONLY:只讀開啟;
  • O_WRONLY:只寫開啟;
  • O_RDWR:讀寫開啟;
上面三個常量必須指定一個且只能指定一個,下列常量則是可選擇的:
  • O_APPEND:每次寫時都追加到檔案的尾端;
  • O_CREAT:若檔案不存在,則建立它。使用此選項時,需要第三個引數mode,用於指定新檔案的訪問許可權位。
  • O_EXCL:若同時指定了O_CREAT,而檔案已經存在,則會出錯。用此可以測試一個檔案是否存在,如果不存在,則建立此檔案。這使得測試和建立成為一個原子操作;
  • O_TRUNC:如果檔案存在,而且為只寫或讀寫成功開啟,則將其長度截短為0;
  • O_NOCTTY:如果pathname指定的是終端裝置,則不將該裝置分配作為此程序的控制終端;
  • O_NONBLOCK:如果pathname指的是一個FIFO、一個塊特殊檔案或一個字元特殊檔案,則此選項為檔案的本次開啟操作和後續I/O操作設定為非阻塞模式;
下面三個標誌也是可選的,他們是SUS中同步輸入和輸出選項的一部分:
  • O_DSYNC:使每次write等物理I/O操作完成,但是如果寫操作並不影響讀取剛寫入的資料,則不等待檔案屬性被更新;
  • O_SYNC:使每次write都等到物理I/O操作完成,包括由write操作引起的檔案屬性更新所需的I/O(資料和屬性總是同步更新);
  • O_RSYNC:使每一個以檔案描述符作為引數的read操作等待,直至任何對檔案同一部分的未決寫操作都完成;

open函式返回的檔案描述符一定是最小的未用描述符數值。這一點被某些應用程式用來在標準輸入、標準輸出或標準錯誤上開啟新的檔案。

檔名與路徑名截短:

在POSIX.1中,常量_POSIX_NO_TRRUNC決定了是否要截短過長的檔名或路徑名,還是返回一個出錯。根據檔案系統型別,此值可變。若_POSIX_NO_TRUNC有效,則在整個路徑名超過PATH_MAX,或路徑名中的任意檔名超過NAME_MAX時,返回出錯狀態,並將errno設定為ENAMETOOLONG。

creat函式:

也可以呼叫creat函式建立一個新檔案:

#include <fcntl.h>

int creat(const char *pathname, mode_t mode);

返回值:若成功,則返回只寫開啟的檔案描述符,若出錯則返回-1。

此函式等效於:open(pathname,O_WRONLY | O_CREAT | O_TRUNC, mode)。creat函式的一個不足之處是它以只寫方式開啟所建立的檔案。

close函式:

可呼叫close函式關閉一個開啟的檔案:

#include <unistd.h>

int close(int fieldes)

返回值:若成功則返回0,若出錯則返回-1。

關閉一個檔案時,還會釋放該程序加在該檔案上的所有記錄鎖。當一個程序終止時,核心自動關閉它所有開啟的檔案。很多程式都利用這一功能而不顯示地用close關閉開啟檔案。

lseek函式:

每個開啟的檔案都有一個與其相關聯的當前檔案偏移量。它通常是個非負整數,用以度量從檔案開始處計算的位元組數。通常,讀寫操作都從當前檔案偏移量處開始,並使偏移量增加所讀寫的位元組數。

按照系統預設情況,當開啟一個檔案時,除非指定O_APPEND選項,否則該偏移量設定為0。可呼叫lseek函式顯式地為一個開啟的檔案設定其偏移量:

#include <unistd.h>

off_t lseek(int fieldes, off_t offset, int whence);

返回值:若成功,則返回新的檔案偏移量,若出錯則返回-1。

對引數offset的解釋與引數whence的值有關:

  • 若whence是SEEK_SET,則將該檔案的偏移量設定為距檔案開始處offset個位元組;
  • 若whence是SEEK_CUR,則將該檔案的偏移量設定為其當前值加offset,offset可為正或負。
  • 若whence是SEEK_END,則將該檔案的偏移量設定為檔案長度加offset,offset可為正或負。

因此可用如下方式確定開啟檔案的當前偏移量:

offset currpos

currpos = lseek(fd, 0, SEEK_CUR);

用上述方法還可以確定所涉及的檔案是否可以設定偏移量。如果檔案描述符引用的是一個管道,FIFO或網路套接字,則leek返回-1,並將errno設定為ESPIPE。

下列程式用於測試能否對其標準輸入設定偏移值:

/*
 * Copyright (C) [email protected]
 */


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


int
main(void)
{
	if (lseek(STDIN_FILENO, 0, SEEK_CUR) < 0) {
		printf("can't seek\n");
	} else {
		printf("seek ok\n");
	}
	exit(0);
}

通常檔案的當前偏移量應當是個非負整數,但是某些裝置也可能允許負的偏移量。但是對於普通檔案,其偏移量必須是非負值。lseek僅將當前的檔案偏移量記錄在核心中,它並不引起任何I/O操作。

檔案偏移量可以大於檔案的當前長度,這種情況下,對該檔案的下一次寫將加長該檔案,並在檔案中構成一個空洞。位於檔案中但沒有寫過的位元組都被讀為0。檔案中的空洞並不要求在磁碟上佔用儲存區,具體處理方式與檔案系統的實現有關。

下列程式用於建立一個具有空洞的檔案:

/*
 * Copyright (C) [email protected]
 */


#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>


#define FILE_NAME "file.hole"


int
main(void)
{
	char buf_one[] = "abcdefghij";
	char buf_two[] = "ABCDEFGHIJ";
	int fd;

	if ( (fd = creat(FILE_NAME, S_IRUSR | S_IWUSR)) < 0) {
		printf("create file %s error: %s\n", FILE_NAME, strerror(errno));
		exit(1);
	} 
	
	if (write(fd, buf_one, 10) != 10) {
		printf("write error: %s\n", strerror(errno));
		exit(2);
	}
	
	if (lseek(fd, 16384, SEEK_SET) == -1) {
		printf("lseek error: %s\n", strerror(errno));
		exit(3);
	}

	if (write(fd, buf_two, 10) != 10) {
		printf("write error: %s\n", strerror(errno));
		exit(4);
	}

	exit(0);
}

read函式:

呼叫read函式從開啟檔案中讀取資料:

#include <unistd.h>

ssize_t read(int fields, void *buf, size_t nbytes);

返回值:若成功則返回讀到的位元組數,若已到檔案結尾則返回0,若出錯返回-1。

ssize_t和size_t都是基本系統資料型別,ssize_t為帶符號的整數,size_t為不帶符號的整數。

有多種情況可使實際讀到的位元組數少於要求讀的位元組數:

  • 在讀普通檔案時,在讀到要求的位元組數之前已經到達了檔案尾端;
  • 從終端裝置讀時,通常一次最多讀一行;
  • 當從網路讀時,網路中的緩衝機構可能造成返回值小於所要求讀的位元組數;
  • 當從管道或FIFO讀時,如果管道包含的位元組少於所需的數量,那麼read將只返回實際可用的位元組數;
  • 當從某些面向記錄的裝置(例如磁帶)讀時,一次最多返回一個記錄;
  • 當某一訊號造成中斷,而已經讀了部分資料量時;

write函式:

呼叫write函式向開啟的檔案寫資料:

#include <unistd.h>

sszie_t write(int fields,const void *buf, size_t nbytes)

若成功則返回已寫的位元組數,若出錯則返回-1。

其返回值通常與引數nbytes的值相同,否則表示出錯。write出錯的一個常見原因是:磁碟已寫滿,或者超過了一個給定程序的檔案長度限制。

I/O的效率:

下列程式使用read和write函式複製一個檔案:

/*
 * Copyright (C) [email protected]
 */


#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>


#define BUFSIZE 4096


int
main(void)
{
	int n;
	char buf[BUFSIZE];

	while ( (n = read(STDIN_FILENO, buf, BUFSIZE)) > 0) {
		if (write(STDOUT_FILENO, buf, n) != n) {
			printf("write error: %s\n", strerror(errno));
			exit(1);
		}
	}

	if (n < 0) {
		printf("read error: %s\n", strerror(errno));
		exit(2);
	}

	exit(0);
}

不同的BUFSIZE對程式的執行時間有非常大的影響。系統CPU時間的最小值出現在BUFFSIZE為4096處(一個block的大小),繼續增大緩衝區長度對此時間幾乎沒有影響。


相關推薦

UNIX環境高階程式設計5檔案I/O1

UNIX系統中的大多數檔案I/O只需要用到5個函式:open、read、write、lseek以及close。本章說明的函式經常稱為“不帶緩衝的I/0”,術語不帶緩衝指的是每個read和write都呼

UNIX環境高階程式設計學習之路(三)----標準I/O

#include <stdio.h> #include <wchar.h> int fwide(FILE *fp, int mode); 返回值:若流是寬定向的,返回 正值;若流是位元組定向的,返回負值;若流是未定向的,返回0;</pre></div><

Groovy基本使用5檔案I/O 處理

Groovy 檔案I/O 處理Groovy 中處理檔案 I/O 時,除了可以使用 Java 本身的IO類,如:    java.io.File, java.io.InputStream,java.io.

UNIX環境高階程式設計學習之路(二)----檔案和目錄

對於UNIX環境程式設計,工作中經常會用到相關知識,作為學習UNIX環境程式設計的經典書籍--UNIX環境高階程式設計,是每個UNIX程式設計人員必看的經典書籍之一,為了將相關知識重新進行學習,以系統的整合所學知識,遂以博文形式作為總結。 一、概述 本章將描述檔案系統

UNIX環境高階程式設計1UNIX基礎知識1

本系列文章是學習被譽為UNIX程式設計聖經的《UNIX環境高階程式設計》的讀書筆記。《UNIX環境高階程式設計》的英文全稱為《Advanced Programming in the UNIX Envi

unix環境高階程式設計》 讀書筆記 5

date and time 涉及到的函式列出如下,然後再舉例執行,輸出結果,比較直觀。 時間這塊資料有限,如果有誤,還望指正。 #include <time.h>

UNIX環境高階程式設計的學習

UNIX系統程序控制程式說明 該程式從標準輸入讀取命令,然後執行這些命令,程式涉及到的函式主要用法包括: fgets的用法 execlp的用法 waitpid的用法 程式如下: #include "apue.h" #include <sys/wai

UNIX環境高階程式設計的學習

UNIX環境高階程式設計第一個例子的編譯 這本書中有很多的例子,為了加深理解,習慣性自己敲一遍程式碼然後看執行結果,再去理解其中的知識點,但是在虛擬機器下如何編譯這些程式碼呢,需要以下幾步: 下載並解壓縮apue.3e檔案包 在“apue.h”中最後一行加:#inclu

UNIX環境高階程式設計第三版 第五章筆記

5.2 流和物件 只有兩個函式可以改變流的定向: freopen函式清楚一個流的定向,fwide函式可用於設定一個流的定向。 #include <stdio.h> #include <wchar.h> int fwide(FILE

unix環境高階程式設計第三版-讀書筆記2

限制 unix系統實現定義了很多幻數和常量,其中有很多已被硬編碼到程式中,或用特定的技術確定。 已下兩種型別是必須的。 (1)編譯時限制(例如,短整型的最大值是什麼?) (2)執行時限制(例如,檔名有多少個字串?) 編譯時限制可在標頭檔案中定義。程式在編譯時包含這些標頭檔案。但是執行

UNIX環境高階程式設計學習筆記程序控制

1.程序標識 每個程序都有一個非負整型表示的唯一程序ID。因為程序ID識別符號總是唯一的,常將其用作其他識別符號的一部分以保證其唯一性。程序ID是可複用的,當一個程序終止後,其程序ID就成為複用的候選者。 ID為0的程序通常是排程程序,常常被稱為交換程序(s

檔案和目錄--unix環境高階程式設計

     普通檔案和目錄linux中最多的兩類檔案,linux中一共有七種型別的檔案,如下:1.普通檔案 2.目錄 3.字元特殊裝置 4.塊特殊裝置 5.FIFO,又叫命名管道 6.Socket,即套接字 7.符號連結 獲取一個檔案的詳細資訊可以使用stat函式組,stat

Unix環境高階程式設計》總結

程序環境 (第七章) 1、程序終止方式 正常終止: - main函式返回 - exit() - _exit() 或 _Exit() - 最後一個執行緒返回 - 最後一個執行緒呼叫pthread_exit() 異常終止: - abort() -

[UNIX環境高階程式設計] 檔案和目錄

1 引言 上文圍繞了普通檔案I/O進行了討論——開啟檔案、讀檔案或寫檔案。本文將描述檔案系統的其他特徵和檔案的性質。將從stat函式開始,stat結構中的大多數成員都是基本系統資料型別,逐個分解stat結構的每一個成員以瞭解檔案的所有屬性。 使用stat函式最多的地方可能就是[ls -l

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

1.引言 UNIX系統中的大多數檔案I/O只需要用到5個函式:open、read、write、lseek以及close,這裡所涉及到的函式經常被稱為不帶緩衝的I/O1。只要涉及在多個程序之間共享資源,原子操作的概念就變得非常重要,我們將通過I/O和open函式的引數來討論此概念,dup、

UNIX環境高階程式設計(三) 第六章

6 系統資料檔案和資訊 6.2 口令檔案 #include <pwd.h> struct passwd *getpwuid(uid_t uid); /* 檢視使用者登入名 */ struct passwd *getpwnam(const

UNIX環境高階程式設計(3) 第三章

3 檔案I/O 3.1 引言 3.2 檔案描述符 檔案描述符是一個標示,非負整數,類似於windows裡的控制代碼,為了與標準C保持一致(標準C裡的檔案的讀寫都是通過File Pointer)UNIX採用了這樣的三級結構,我混淆於檔案描述標誌和檔案

UNIX環境高階程式設計(3) 第八章

8 程序控制 8.1 引言 8.2 程序標識 #include <unistd.h> pid_t getpid(void); return: 呼叫程序的程序ID pid_t getppid(void

UNIX環境高階程式設計(3) 第二章

2.2UNIX標準化 2.2.1 ISO C 國際標準化組織(International Organization for Standardization,ISO) 國際電子技術委員會(International Electrotechnical Co

UNIX環境高階程式設計(3) 第一章

1.1 引言 所有作業系統都為他們所執行的程式提供服務。典型的服務包括:執行新程式、開啟檔案、讀檔案、分配儲存區以及獲取當前時間等。 1.2 UNIX體系結構 層級從裡向外擴充套件應用。 1. 核心:可將作業系統定義為一種軟體,即稱為核心,它控制