1.Linux文件系統
Linux支持多種文件系統,如ext、ext2、minix、iso9660、msdos、fat、vfat、nfs等。在這些具體文件系統的上層,Linux提供了虛擬文件系統(VFS)來統一它們的行爲,虛擬文件系統爲不同的文件系統與內核的通信提供了一致的接口。下圖給出了Linux中文件系統的關係:
在Linux平臺下對文件編程可以使用兩類函數:(1)Linux操作系統文件API;(2)C語言I/O庫函數。 前者依賴於Linux系統調用,後者實際上與操作系統是獨立的,因爲在任何操作系統下,使用C語言I/O庫函數操作文件的方法都是相同的。本章將對這兩種方法進行實例講解。
2.Linux文件API
Linux的文件操作API涉及到創建、打開、讀寫和關閉文件。
創建
int creat(const char *filename, mode_t mode);
參數mode指定新建文件的存取權限,它同umask一起決定文件的最終權限(mode&umask),其中umask代表了文件在創建時需要去掉的一些存取權限。umask可通過系統調用umask()來改變:
int umask(int newmask);
該調用將umask設置爲newmask,然後返回舊的umask,它隻影響讀、寫和執行權限。
打開
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
open函數有兩個形式,其中pathname是我們要打開的文件名(包含路徑名稱,缺省是認爲在當前路徑下面),flags可以去下面的一個值或者是幾個值的組合:
標誌 |
含義 |
O_RDONLY |
以只讀的方式打開文件 |
O_WRONLY |
以只寫的方式打開文件 |
O_RDWR |
以讀寫的方式打開文件 |
O_APPEND |
以追加的方式打開文件 |
O_CREAT |
創建一個文件 |
O_EXEC |
如果使用了O_CREAT而且文件已經存在,就會發生一個錯誤 |
O_NOBLOCK |
以非阻塞的方式打開一個文件 |
O_TRUNC |
如果文件已經存在,則刪除文件的內容 |
O_RDONLY、O_WRONLY、O_RDWR三個標誌只能使用任意的一個。
如果使用了O_CREATE標誌,則使用的函數是int open(const char *pathname,int flags,mode_t mode); 這個時候我們還要指定mode標誌,用來表示文件的訪問權限。mode可以是以下情況的組合:
標誌 |
含義 |
S_IRUSR |
用戶可以讀 |
S_IWUSR |
用戶可以寫 |
S_IXUSR |
用戶可以執行 |
S_IRWXU |
用戶可以讀、寫、執行 |
S_IRGRP |
組可以讀 |
S_IWGRP |
組可以寫 |
S_IXGRP |
組可以執行 |
S_IRWXG |
組可以讀寫執行 |
S_IROTH |
其他人可以讀 |
S_IWOTH |
其他人可以寫 |
S_IXOTH |
其他人可以執行 |
S_IRWXO |
其他人可以讀、寫、執行 |
S_ISUID |
設置用戶執行ID |
S_ISGID |
設置組的執行ID |
除了可以通過上述宏進行“或”邏輯產生標誌以外,我們也可以自己用數字來表示,Linux總共用5個數字來表示文件的各種權限:第一位表示設置用戶ID;第二位表示設置組ID;第三位表示用戶自己的權限位;第四位表示組的權限;最後一位表示其他人的權限。每個數字可以取1(執行權限)、2(寫權限)、4(讀權限)、0(無)或者是這些值的和。例如,要創建一個用戶可讀、可寫、可執行,但是組沒有權限,其他人可以讀、可以執行的文件,並設置用戶ID位。那麼,我們應該使用的模式是1(設置用戶ID)、0(不設置組ID)、7(1+2+4,讀、寫、執行)、0(沒有權限)、5(1+4,讀、執行)即10705:
open("test", O_CREAT, 10705);
上述語句等價於:
open("test", O_CREAT, S_IRWXU | S_IROTH | S_IXOTH | S_ISUID );
如果文件打開成功,open函數會返回一個文件描述符,以後對該文件的所有操作就可以通過對這個文件描述符進行操作來實現。
讀寫
在文件打開以後,我們纔可對文件進行讀寫了,Linux中提供文件讀寫的系統調用是read、write函數:
int read(int fd, const void *buf, size_t length);
int write(int fd, const void *buf, size_t length);
其中參數buf爲指向緩衝區的指針,length爲緩衝區的大小(以字節爲單位)。函數read()實現從文件描述符fd所指定的文件中讀取length個字節到buf所指向的緩衝區中,返回值爲實際讀取的字節數。函數write實現將把length個字節從buf指向的緩衝區中寫到文件描述符fd所指向的文件中,返回值爲實際寫入的字節數。
以O_CREAT爲標誌的open實際上實現了文件創建的功能,因此,下面的函數等同creat()函數:
int open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);
定位
對於隨機文件,我們可以隨機的指定位置讀寫,使用如下函數進行定位:
int lseek(int fd, offset_t offset, int whence);
lseek()將文件讀寫指針相對whence移動offset個字節。操作成功時,返回文件指針相對於文件頭的位置。參數whence可使用下述值:
SEEK_SET:相對文件開頭
SEEK_CUR:相對文件讀寫指針的當前位置
SEEK_END:相對文件末尾
offset可取負值,例如下述調用可將文件指針相對當前位置向前移動5個字節:
lseek(fd, -5, SEEK_CUR);
由於lseek函數的返回值爲文件指針相對於文件頭的位置,因此下列調用的返回值就是文件的長度:
lseek(fd, 0, SEEK_END);
關閉
當我們操作完成以後,我們要關閉文件了,只要調用close就可以了,其中fd是我們要關閉的文件描述符:
int close(int fd);
例程:編寫一個程序,在當前目錄下創建用戶可讀寫文件“hello.txt”,在其中寫入“Hello, software weekly”,關閉該文件。再次打開該文件,讀取其中的內容並輸出在屏幕上。
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#define LENGTH 100
main()
...{
int fd, len;
char str[LENGTH];
fd = open("hello.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); /**//* 創建並打開文件 */
if (fd)
...{
write(fd, "Hello, Software Weekly", strlen("Hello, software weekly")); /**//* 寫入Hello, software weekly字符串 */
close(fd);
}
fd = open("hello.txt", O_RDWR);
len = read(fd, str, LENGTH); /**//* 讀取文件內容 */
str[len] = '';
printf("%s ", str);
close(fd);
}
3.C語言庫函數
C庫函數的文件操作實際上是獨立於具體的操作系統平臺的,不管是在DOS、Windows、Linux還是在VxWorks中都是這些函數:
創建和打開
FILE *fopen(const char *path, const char *mode);
fopen()實現打開指定文件filename,其中的mode爲打開模式,C語言中支持的打開模式如下表:
標誌 |
含義 |
r, rb |
以只讀方式打開 |
w, wb |
以只寫方式打開。如果文件不存在,則創建該文件,否則文件被截斷 |
a, ab |
以追加方式打開。如果文件不存在,則創建該文件 |
r+, r+b, rb+ |
以讀寫方式打開 |
w+, w+b, wh+ |
以讀寫方式打開。如果文件不存在時,創建新文件,否則文件被截斷 |
a+, a+b, ab+ |
以讀和追加方式打開。如果文件不存在,創建新文件 |
其中b用於區分二進制文件和文本文件,這一點在DOS、Windows系統中是有區分的,但Linux不區分二進制文件和文本文件。
讀寫
C庫函數支持以字符、字符串等爲單位,支持按照某中格式進行文件的讀寫,這一組函數爲:
int fgetc(FILE *stream);
int fputc(int c, FILE *stream);
char *fgets(char *s, int n, FILE *stream);
int fputs(const char *s, FILE *stream);
int fprintf(FILE *stream, const char *format, ...);
int fscanf (FILE *stream, const char *format, ...);
size_t fread(void *ptr, size_t size, size_t n, FILE *stream);
size_t fwrite (const void *ptr, size_t size, size_t n, FILE *stream);
fread()實現從流stream中讀取加n個字段,每個字段爲size字節,並將讀取的字段放入ptr所指的字符數組中,返回實際已讀取的字段數。在讀取的字段數小於num時,可能是在函數調用時出現錯誤,也可能是讀到文件的結尾。所以要通過調用feof()和ferror()來判斷。
write()實現從緩衝區ptr所指的數組中把n個字段寫到流stream中,每個字段長爲size個字節,返回實際寫入的字段數。
另外,C庫函數還提供了讀寫過程中的定位能力,這些函數包括
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, const fpos_t *pos);
int fseek(FILE *stream, long offset, int whence);
等。
關閉
int fclose (FILE *stream);
例程:將第2節中的例程用C庫函數來實現。
#define LENGTH 100
main()
...{
FILE *fd;
char str[LENGTH];
fd = fopen("hello.txt", "w+"); /**//* 創建並打開文件 */
if (fd)
...{
fputs("Hello, Software Weekly", fd); /**//* 寫入Hello, software weekly字符串 */
fclose(fd);
}
fd = fopen("hello.txt", "r");
fgets(str, LENGTH, fd); /**//* 讀取文件內容 */
printf("%s ", str);
fclose(fd);
}
4.小結
Linux提供的虛擬文件系統爲多種文件系統提供了統一的接口,Linux的文件編程有兩種途徑:基於Linux系統調用;基於C庫函數。這兩種編程所涉及到文件操作有新建、打開、讀寫和關閉,對隨機文件還可以定位。本章對這兩種編程方法都給出了具體的實例。