Contents

Linux Chapter 4 - 檔案IO

檔案I/O

概述

全部執行I/O的系統呼叫都是透過檔案描述符來控制開啟的檔案,各類型的檔案在開啟之後,都能以檔案描述符進行操控。

標準檔案符

shell平時會將以下三個檔案描述符保持一直開啟,可以使用I/O redirection進行適當的修改

file descriptor 用途 POSIX 名稱 stdio stream
0 標準輸入 STDIN_FILENO stdin
1 標準輸出 STDOUT_FILENO stdout
2 標準錯誤 STDERR_FILENO stderr

開啟檔案

open()

1
2
3
4
5
#include<sys/stat.h>
#include<fcntl.h>
int open(const char *pathname, int flags, ... /*mode_t mode*/);
                                            
                 Return file descriptor on success, -1 on error

flags -> 為位元mask 指定檔案的存取模式 mode -> 指定了檔案的存取權限(如果open()沒有指定O_CREATflags可以省略)

  • 由於flags各個參數互相獨立(除了必選項不可重複)皆可以使用|來新增性質

必選項:以下三個常數中必須指定一個,且僅允許指定一個。

flags 用途
O_RDONLY 以唯讀模式開啟
O_WRONLY 以唯寫模式開啟
O_RDWR 以讀寫模式開啟

以可讀寫方式打開文件。上述三種旗標是互斥的,也就是不可同時使用,但可與下列的旗標利用OR(|)運算符組合。

以下可選項可以同時指定0個或多個,和必選項按位|起來作為flags參數。

flags 用途
O_CLOEXEC 設定close-on-exec flags
O_CREAT 若檔案不存在則建立
O_DIRECTORY 如果pathname不是目錄,失敗
O_EXCL 結合O_CREAT參數使用,專門用來建立檔案,若創建文件時,文件已存在,會返回錯誤訊息
O_LARGEFILE 在32位元系統中使用,可以開啟大檔案
O_NOCTTY 不要讓pathname所指向的終端設備,成為控制終端機
O_NOFOLLOW 如果參數pathname 所指的文件為一符號連接,則會令打開文件失敗
O_TRUNC 若文件存在並且以可寫的方式打開時,此旗標會令文件長度清為0,而原來存於該文件的資料也會消失。
O_APPEND 總在檔案結尾新增資料
O_ASYNC 進行I/O操作時,產生signal
O_DIRECT 檔案I/O跳過 buffer cache
O_DSYNC 提供同步I/O的資料完整性
O_NOATIME read()時不更新最近的存取時間
O_NONBLOCK 以非阻塞的方式開啟
O_SYNC 以同步的方式寫入檔案

詳細內容可以參考The linux programming interface 國際中文版p.84

man page : https://man7.org/linux/man-pages/man2/open.2.html

創建檔案

creat() 根據pathname建立並開啟一個檔案,若檔案存在=>開啟並清空檔案內容

1
2
#include<fcntl.h>
int creat(const char* pathname, mode_t mode);

可以等價於以下的open()函數

1
fd = open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);

讀取檔案

read()

1
2
3
4
#include<unist.h>
ssize_t read(int fd, void *buffer, size_t count);

/*returns number of bytes read,0 on EOF,or -1 on error*/

count 指定最多可以讀取的byte數量 buffer 提供用來存放輸入資料的buffer cache位址 buffer cache至少有count個bytes

man page : https://man7.org/linux/man-pages/man2/read.2.html

寫入檔案

write()

1
2
3
4
#include<unistd.h>
ssize_t write(int fd, const void *buffer, size_t count);

                Return number of bytes written, or -1 on error

count 指定最多可以要從buffer寫入檔案的byte數量 buffer 要寫入檔案的資料bytes數

關閉檔案

1
2
3
#include<unistd.h>
int close(int fd);
                    Return 0 on success, or -1 on error

改變檔案偏移量

有時候也稱為讀寫偏移量或指標。指的是執行下一個read() or write()操作的檔案位置。檔案開啟時或指向開頭(偏移量 = 0) 針對fd參數所代表的已開啟檔案,可以使用lseek() function

1
2
3
#include<unistd.h>
off_t lseek(int fd,off_t offset, int whence);
                Return new file offset if successful, or -1 on error

offset指定以byte為單位的數值,whence表示應參考哪個基準點來解釋offset參數,如下列表格

whence 用途
SEEK_SET 將檔案偏移量從檔案的起始點開始算 offset 個 bytes
SEEK_CUR 相對於目前的偏移量,調整offset個 bytes
SEEK_END 將檔案偏移量設定為檔案大小加上offset 個 bytes,可以說offset應從檔案最後一個byte之後的下一個byte開始算起

一些lseek()的應用案例

1
2
3
4
5
lseek(fd,0,SEEK_SET);     /*Start of file*/
lseek(fd,0,SEEK_END);     /*Next byte after the end of the file*/
lseek(fd,-1,SEEK_END);    /*Last byte of file*/
lseek(fd,-10,SEEK_CUR);   /*Ten bytes prior to current location*/
lseek(fd,10000,SEEK_END); /*10001 past last byte of file*/

lseek()不允許應用於pipe,FIFO,socket or terminal

檔案空洞(file hole)

如果檔案偏移量跨越了檔案結尾,然後再執行I/O操作,read()會return 0,但write()可以在檔案結尾後任意寫資料進去。 從檔案結尾之後到新寫入資料之間的這段空間就稱為檔案空洞。

  • 檔案空洞不佔用任何空間,優勢在於相較於為實際需要的空bytes分配磁碟區塊,稀疏填充的檔案會佔用較少的空間。

以下為apue範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include "apue.h"
#include <fcntl.h>

char	buf1[] = "abcdefghij";
char	buf2[] = "ABCDEFGHIJ";

int
main(void)
{
	int	fd;

	if ((fd = creat("file.hole", FILE_MODE)) < 0)
		err_sys("creat error");

	if (write(fd, buf1, 10) != 10)
		err_sys("buf1 write error");
	/* offset now = 10 */

	if (lseek(fd, 16384, SEEK_SET) == -1)
		err_sys("lseek error");
	/* offset now = 16384 */

	if (write(fd, buf2, 10) != 10)
		err_sys("buf2 write error");
	/* offset now = 16394 */

	exit(0);
}

running result

1
2
3
4
5
6
7
8
9
$ ./a.out
$ ls -l file.hole
-rw-r--r-- 1 sar    16394   <date>  file.hole
$ od -c file.hole
0000000  a b c d e f g h i j \0 \0 \0 \0 \0 \0
0000020 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0040000  A B C D E F G H I J
0040012

可以使用du -h 指令 => 顯示檔案佔用的block是多少