POSIX文件I/O編程

一、POSIX文件I/O與ASCI文件I/O

POSIX表示可移植操作系統接口(Portable Operating System Interface of UNIX,縮寫爲 POSIX ),不帶緩存的文件IO操作,於直接調用系統調用(system call)的方式,高效完成文件輸入輸出。它以文件標識符(整型)作爲文件唯一性的判斷依據。

ASCI文件I/O:帶緩存的文件IO操作,效率低但是易於維護。它以文件指針(FILE*)作爲文件唯一性的判斷依據。

二、靜態文件與動態文件

文件平時存儲在塊存儲設備(類似於硬盤)中的文件系統中(靜態文件),open打開一個文件時,linux內核在進程中建立一個打開文件的數據結構,記錄打開的文件的信息;內核在內存中申請建立一段內存,並將靜態文件的內容從塊存儲設備讀取到特定地址管理存放(動態文件)。

打開文件後對這個文件的讀寫操作都是針對內存中的動態文件,當對動態文件進行讀寫後,動態文件和塊存儲設備中的靜態文件不同步,close時關閉動態文件,內核將內存中的動態文件的內容同步到塊存儲設備的靜態文件。

三、操作文件的一般步驟

先open打開一個文件,得到一個文件描述符,然後對文件進行write/read操作(或其他操作),最後close關閉文件即可。

文件描述符其實實質是一個數字,當我們open打開一個文件時,操作系統在內存中構建了一些數據結構來表示這個動態文件,然後返回給應用程序一個數字作爲文件描述符,這個數字就和我們內存中維護這個動態文件的這些數據結構掛鉤綁定上了,以後我們應用程序如果要操作這一個動態文件,只需要用這個文件描述符進行區分。文件描述符的作用域就是當前進程,出了當前進程這個文件描述符就沒有意義了。在遵從POSIX的應用程序中,文件描述符0、1、2分別對應STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,因此一個應用程序進程中文件描述符總是從3開始的。

3.1 創建/打開/關閉文件

(1)open()函數

POSIX使用open()函數打開一個文件,使用creat()函數創建一個新文件。open()函數在指定一定參數的情況下,會隱含調用creat()函數創建文件。

int open(const char *file_name, int flags)
int open(const char *file_name, int flags, mode_t mode)
  • file_name: 欲打開的文件名(可包含路徑)
  • mode: 創建文件的權限,在嵌入式開發中,一般使用默認權限。

            使用四個數字指定創建文件的權限,與linux的權限設置相同,如0755

  • flags:指定了打開文件的方式,可以用C語言的“或”操作符指定多個參數
flags description
O_RDONLY 只讀
O_WRONLY 只寫
O_RDWR 讀寫
O_CREAT 若欲打開的文件不存在,則創建該文件 
O_APPEND 追加的方式打開(定位到文件尾部)
O_TRUNC 若文件存在,則長度被截爲0
O_EXCL 若O_CREAT也設置,此指令檢查文件是否存在:若不存在則建立新文件;若存在則報錯
O_LARGEFILE 在32bit系統中,支持大於2G的文件的打開
O_DIRECTORY 若打開的文件不是目錄,則報錯
O_NONBLOCK 對文件的操作使用非阻塞模式。此方式對FIFO,特殊塊設備文件,或特殊字符設備文件有效。
O_SYNC 使每次write都等到物理I/O操作完成,包括由write操作引起的文件屬性更新所需的I/O。

注意:

(1)O_TRUNC與O_APPEND

  • O_TRUNC屬性去打開文件時,如果這個文件中本來是有內容的,則原來的內容會被全部丟棄
  • O_APPEND屬性去打開文件時,如果這個文件中本來是有內容的,則新寫入的內容會接續到原來內容的後面。如果O_APPEND和O_TRUNC同時出現,只有O_TRUNC起作用。

(2)O_CREAT與O_EXCL

  • open中加入O_CREAT後,不管原來這個文件存在與否都能打開成功。假如我們希望的效果是:如果我CREAT要創建的是一個已經存在的名字的文件,則給我報錯,不要去創建,就要靠O_EXCL標誌和O_CREAT標誌來結合使用。當O_EXCL和O_CREAT聯合使用open一個已經存在的文件,則會報錯,打開失敗。
  • open函數在使用O_CREAT標誌去創建文件時,可以使用第三個參數mode來指定要創建的文件的權限。

(3)O_NONBLOCK

  • 如果一個函數是阻塞式的,則我們調用這個函數時當前進程有可能被卡住(阻塞住,實質是這個函數內部要完成的事情條件不具備,當前沒法做,要等待條件成熟),函數被阻塞住了就不能立刻返回;
  • 如果一個函數是非阻塞式的那麼我們調用這個函數後一定會立即返回,但是函數有沒有完成任務不一定。阻塞和非阻塞是兩種不同的設計思路,並沒有好壞。總的來說,阻塞式的結果有保障但是時間沒保障;非阻塞式的時間有保障但是結果沒保障。
  • 我們打開一個文件默認就是阻塞式的,如果你希望以非阻塞的方式打開文件,則flag中要加O_NONBLOCK標誌。

(4)O_SYNC  

  • write阻塞等待底層完成寫入才返回到應用層。
  • 無O_SYNC時write只是將內容寫入底層緩衝區即可返回,然後底層(操作系統中負責實現open、write這些操作的那些代碼,也包含OS中讀寫硬盤等底層硬件的代碼)在合適的時候會將buf中的內容一次性的同步到硬盤中。這種設計是爲了提升硬件操作的性能和銷量,提升硬件壽命;但是有時候我們希望硬件不好等待,直接將我們的內容寫入硬盤中,這時候就可以用O_SYNC標誌。

REF:

https://www.cnblogs.com/suzhou/p/5381738.html

(2) creat()函數

creat()函數的作用是創建新文件,這個基本已經棄用,此函數是爲了兼容早期UNIX系統的open()函數。

(3) close()函數

#include <unistd.h>

int close(int fd);

關閉文件描述符fd指向的動態文件,並存儲文件和刷新緩存。

文件關閉成功返回0,有錯誤返回-1;

 

3.2 讀寫文件

(1) read函數

讀取文件內容,並返回實際讀取到的字節數

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count)
  • fd: 讀取的文件的文件描述符
  • buf: 暫存區(暫時存儲讀取到的文件內容)
  • count: 要讀取的字節數

返回值是零並不代表讀取失敗,是讀到末尾

讀取失敗是-1

(2) write函數

向文件中寫入一定的內容,並返回實際寫入文件的字節數

#include <unistd.h>
ssize_t write(int fd, void *buf, size_t count)
  • fd: 寫入的文件的文件描述符
  • buf: 暫存區(將該暫存區的內容寫入到文件)
  • count: 需要寫入的字節數

3.3 文件定位

  • 在動態文件中,我們會通過文件指針來表徵這個正在操作的位置。所謂文件指針,就是我們文件管理表(打開文件表)這個結構體裏面的一個指針。所以文件指針其實是vnode中的一個元素。這個指針表示當前我們正在操作文件流的哪個位置。這個指針不能被直接訪問,linux系統用lseek函數來訪問這個文件指針。
  • 當我們打開一個空文件時,默認情況下文件指針指向文件流的開始。所以這時候去write時寫入就是從文件開頭開始的。write和read函數本身自帶移動文件指針的功能,所以當我write了n個字節後,文件指針會自動向後移動n位。如果需要人爲的隨意更改文件指針,那就只能通過lseek函數了
  • read和write函數都是從當前文件指針處開始操作的,所以當我們用lseek顯式的將文件指針移動後,那麼再去read/write時就是從移動過後的位置開始的。
#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence)

修改文件的讀寫位置,並返回當前文件指針所在的位置

  • fd: 寫入的文件的文件描述符
  • offset: 相對於第三個參數whence的偏移量
  • whence:
    whence description
    SEEK_SET 文件開始位置
    SEEK_CUR 文件當前指針位置
    SEEK_END 文件結束位置
  • 返回值
    成功:文件指針相對開始位置的偏移量(bytes)
    失敗:-1

 

綜合示例:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <unistd.h>

#include <stdio.h>
#include <string.h>

int main(void)
{
	int fd = -1;		// fd 就是file descriptor,文件描述符
	char buf[100] = {0};
	char writebuf[20] = "l love linux";
	int ret = -1;
		// 第一步:打開文件
	fd = open("c.txt", O_RDWR|O_CREAT);//
	if (-1 == fd)		// 有時候也寫成: (fd < 0)
	{
		perror("文件打開錯誤");
		_exit(-1);
	}
	else
	{
		printf("文件打開成功,fd = %d.\n", fd);
	}

	// 寫文件
	ret = write(fd, writebuf, strlen(writebuf));
	if (ret < 0)
	{
		perror("write失敗");
		_exit(-1);
	}
	else
	{
		printf("write成功,寫入了%d個字符\n", ret);
	}


    //移動文件指針到開頭,因爲經過寫入之後,文件指針是在末尾,直接讀的話是讀不到內容的
	ret = lseek(fd, 0, SEEK_SET);//

	// 讀文件
	ret = read(fd, buf, 5);
	if (ret < 0)
	{
		perror("read失敗");
		_exit(-1);
	}
	else
	{
		printf("實際讀取了%d字節.\n", ret);
		printf("文件內容是:[%s].\n", buf);
	}

	//關閉文件
	close(fd);

	return 0;
}

 

REF:

https://www.cnblogs.com/Jimmy1988/p/7488709.html

https://blog.51cto.com/9291927/1796527

https://www.jb51.net/article/94783.htm

https://www.xuebuyuan.com/1389112.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章