Linux下的C编程实战之文件系统编程
在Linux平臺下對文件編程可以使用兩類函數(shù):(1)Linux操作系統(tǒng)文件API;(2)C語言I/O庫函數(shù)。前者依賴于Linux系統(tǒng)調(diào)用,后者實(shí)際上與操作系統(tǒng)是獨(dú)立的,因?yàn)樵谌魏尾僮飨到y(tǒng)下,使用C語言I/O庫函數(shù)操作文件的方法都是相同的。本章將對這兩種方法進(jìn)行實(shí)例講解。
1. 文件I/O操作
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP
? fd=open("/dev/globalvar", O_RDWR, S_IRUSR|S_IWUSR); ? ? //可讀寫方式打開設(shè)備文件
S_IRUSR
Permits the file's owner to read it.
S_IWUSR
Permits the file's owner to write to it.
S_IRGRP
Permits the file's group to read it.
S_IWGRP
Permits the file's group to write to it.
S_ISDIR ( ) 目錄文件
S_ISCHR ( ) 字符特殊文件
S_ISBLK ( ) 塊特殊文件
S_ISFIFO ( ) 管道或F I F O
S_ISLNK ( ) 符號連接( P O S I X . 1或S V R 4無此類型)
S_ISSOC K ( ) 套接字(P O S I X . 1或S V R 4無此類型)
S_ISREG ( ) 普通文件
2.Linux 文件 API
Linux 的文件操作 API 涉及到創(chuàng)建、打開、讀寫和關(guān)閉文件。
創(chuàng)建
int creat(const char *filename, mode_t mode);
參數(shù)mode指定新建文件的存取權(quán)限,它同umask一起決定文件的最終權(quán)限(mode&umask),其中umask代表了文件在創(chuàng)建時(shí)需要去掉的一些存取權(quán)限。umask可通過系統(tǒng)調(diào)用umask()來改變:
int umask(int newmask);
該調(diào)用將umask設(shè)置為newmask,然后返回舊的umask,它只影響讀、寫和執(zhí)行權(quán)限。
打開
int open(const char *pathname, int flags);?
int open(const char *pathname, int flags, mode_t mode);
open函數(shù)有兩個(gè)形式,其中pathname是我們要打開的文件名(包含路徑名稱,缺省是認(rèn)為在當(dāng)前路徑下面),flags可以去下面的一個(gè)值或者是幾個(gè)值的組合:
標(biāo)志含義
O_RDONLY以只讀的方式打開文件
O_WRONLY以只寫的方式打開文件
O_RDWR以讀寫的方式打開文件
O_APPEND以追加的方式打開文件
O_CREAT 創(chuàng)建一個(gè)文件
O_EXEC如果使用了O_CREAT而且文件已經(jīng)存在,就會發(fā)生一個(gè)錯(cuò)誤
O_NOBLOCK以非阻塞的方式打開一個(gè)文件
O_TRUNC如果文件已經(jīng)存在,則刪除文件的內(nèi)容
? ?
O_RDONLY、O_WRONLY、O_RDWR三個(gè)標(biāo)志只能使用任意的一個(gè)。
如果使用了O_CREATE標(biāo)志,則使用的函數(shù)是int open(const char *pathname,int flags,mode_t mode);這個(gè)時(shí)候我們還要指定mode標(biāo)志,用來表示文件的訪問權(quán)限。mode可以是以下情況的組合:
標(biāo)志含義
S_IRUSR 用戶可以讀
S_IWUSR 用戶可以寫
S_IXUSR 用戶可以執(zhí)行
S_IRWXU用戶可以讀、寫、執(zhí)行
S_IRGRP 組可以讀
S_IWGRP 組可以寫
S_IXGRP 組可以執(zhí)行
S_IRWXG 組可以讀寫執(zhí)行
S_IROTH 其他人可以讀
S_IWOTH 其他人可以寫
S_IXOTH 其他人可以執(zhí)行
S_IRWXO其他人可以讀、寫、執(zhí)行
S_ISUID 設(shè)置用戶執(zhí)行ID
S_ISGID 設(shè)置組的執(zhí)行ID
除了可以通過上述宏進(jìn)行“或”邏輯產(chǎn)生標(biāo)志以外,我們也可以自己用數(shù)字來表示,Linux總共用5個(gè)數(shù)字來表示文件的各種權(quán)限:第一位表示設(shè)置用戶ID;第二位表示設(shè)置組ID;第三位表示用戶自己的權(quán)限位;第四位表示組的權(quán)限;最后一位表示其他人的權(quán)限。每個(gè)數(shù)字可以取1(執(zhí)行權(quán)限)、2(寫權(quán)限)、4(讀權(quán)限)、0(無)或者是這些值的和。例如,要創(chuàng)建一個(gè)用戶可讀、可寫、可執(zhí)行,但是組沒有權(quán)限,其他人可以讀、可以執(zhí)行的文件,并設(shè)置用戶ID位。那么,我們應(yīng)該使用的模式是1(設(shè)置用戶ID)、0(不設(shè)置組ID)、7(1+2+4,讀、寫、執(zhí)行)、0(沒有權(quán)限)、5(1+4,讀、執(zhí)行)即10705:
open("test", O_CREAT, 10705);
上述語句等價(jià)于:
open("test", O_CREAT, S_IRWXU | S_IROTH | S_IXOTH | S_ISUID );
如果文件打開成功,open函數(shù)會返回一個(gè)文件描述符,以后對該文件的所有操作就可以通過對這個(gè)文件描述符進(jìn)行操作來實(shí)現(xiàn)。
讀寫
在文件打開以后,我們才可對文件進(jìn)行讀寫了,Linux中提供文件讀寫的系統(tǒng)調(diào)用是read、write函數(shù):
int read(int fd, const void *buf, size_t length);
int write(int fd, const void *buf, size_t length);
其中參數(shù)buf為指向緩沖區(qū)的指針,length為緩沖區(qū)的大小(以字節(jié)為單位)。函數(shù)read()實(shí)現(xiàn)從文件描述符fd所指定的文件中讀取length個(gè)字節(jié)到buf所指向的緩沖區(qū)中,返回值為實(shí)際讀取的字節(jié)數(shù)。函數(shù)write實(shí)現(xiàn)將把length個(gè)字節(jié)從buf指向的緩沖區(qū)中寫到文件描述符fd所指向的文件中,返回值為實(shí)際寫入的字節(jié)數(shù)。
以O_CREAT為標(biāo)志的open實(shí)際上實(shí)現(xiàn)了文件創(chuàng)建的功能,因此,下面的函數(shù)等同creat()函數(shù):
int open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);
定位
對于隨機(jī)文件,我們可以隨機(jī)的指定位置讀寫,使用如下函數(shù)進(jìn)行定位:
int lseek(int fd, offset_t offset, int whence);
lseek()將文件讀寫指針相對whence移動offset個(gè)字節(jié)。操作成功時(shí),返回文件指針相對于文件頭的位置。參數(shù)whence可使用下述值:
SEEK_SET:相對文件開頭
SEEK_CUR:相對文件讀寫指針的當(dāng)前位置
SEEK_END:相對文件末尾
offset可取負(fù)值,例如下述調(diào)用可將文件指針相對當(dāng)前位置向前移動5個(gè)字節(jié):
lseek(fd, -5, SEEK_CUR);
由于lseek函數(shù)的返回值為文件指針相對于文件頭的位置,因此下列調(diào)用的返回值就是文件的長度:
lseek(fd, 0, SEEK_END);
關(guān)閉
當(dāng)我們操作完成以后,我們要關(guān)閉文件了,只要調(diào)用close就可以了,其中fd是我們要關(guān)閉的文件描述符:
int close(int fd);
例程:編寫一個(gè)程序,在當(dāng)前目錄下創(chuàng)建用戶可讀寫文件“hello.txt”,在其中寫入“Hello, software weekly”,關(guān)閉該文件。再次打開該文件,讀取其中的內(nèi)容并輸出在屏幕上。
#include <sys/types.h>
#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); /*創(chuàng)建并打開文件 */
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); /*讀取文件內(nèi)容 */
str[len] = '\0';
printf("%s\n", str);
close(fd);
}
3.C語言庫函數(shù)
C庫函數(shù)的文件操作實(shí)際上是獨(dú)立于具體的操作系統(tǒng)平臺的,不管是在DOS、Windows、Linux還是在VxWorks中都是這些函數(shù):
創(chuàng)建和打開
FILE *fopen(const char *path, const char *mode);
fopen()實(shí)現(xiàn)打開指定文件filename,其中的mode為打開模式,C語言中支持的打開模式如下表:
標(biāo)志含義
r, rb以只讀方式打開
w, wb以只寫方式打開。如果文件不存在,則創(chuàng)建該文件,否則文件被截?cái)?/p>
a, ab以追加方式打開。如果文件不存在,則創(chuàng)建該文件
r+, r+b, rb+ 以讀寫方式打開
w+, w+b, wh+以讀寫方式打開。如果文件不存在時(shí),創(chuàng)建新文件,否則文件被截?cái)?/p>
a+, a+b, ab+以讀和追加方式打開。如果文件不存在,創(chuàng)建新文件
其中b用于區(qū)分二進(jìn)制文件和文本文件,這一點(diǎn)在DOS、Windows系統(tǒng)中是有區(qū)分的,但Linux不區(qū)分二進(jìn)制文件和文本文件。
讀寫
C庫函數(shù)支持以字符、字符串等為單位,支持按照某中格式進(jìn)行文件的讀寫,這一組函數(shù)為:
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()實(shí)現(xiàn)從流stream中讀取加n個(gè)字段,每個(gè)字段為size字節(jié),并將讀取的字段放入ptr所指的字符數(shù)組中,返回實(shí)際已讀取的字段數(shù)。在讀取的字段數(shù)小于num時(shí),可能是在函數(shù)調(diào)用時(shí)出現(xiàn)錯(cuò)誤,也可能是讀到文件的結(jié)尾。所以要通過調(diào)用feof()和ferror()來判斷。
write()實(shí)現(xiàn)從緩沖區(qū)ptr所指的數(shù)組中把n個(gè)字段寫到流stream中,每個(gè)字段長為size個(gè)字節(jié),返回實(shí)際寫入的字段數(shù)。
另外,C庫函數(shù)還提供了讀寫過程中的定位能力,這些函數(shù)包括
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, const fpos_t *pos);
int fseek(FILE *stream, long offset, int whence);?
等。
關(guān)閉
利用C庫函數(shù)關(guān)閉文件依然是很簡單的操作:
int fclose (FILE *stream);
例程:將第2節(jié)中的例程用C庫函數(shù)來實(shí)現(xiàn)。
#include <stdio.h>
#define LENGTH 100
main()
{
FILE *fd;
char str[LENGTH];
fd = fopen("hello.txt", "w+"); /*創(chuàng)建并打開文件 */
if (fd)
{
fputs("Hello, Software Weekly", fd); /*寫入Hello, software weekly字符串 */
fclose(fd);
}
fd = fopen("hello.txt", "r");
fgets(str, LENGTH, fd); /*讀取文件內(nèi)容 */
printf("%s\n", str);
fclose(fd);
}
4.小結(jié)
Linux提供的虛擬文件系統(tǒng)為多種文件系統(tǒng)提供了統(tǒng)一的接口,Linux的文件編程有兩種途徑:基于Linux系統(tǒng)調(diào)用;基于C庫函數(shù)。這兩種編程所涉及到文件操作有新建、打開、讀寫和關(guān)閉,對隨機(jī)文件還可以定位。本章對這兩種編程方法都給出了具體的實(shí)例。
帶緩沖I/O 和 不帶緩沖I/O詳解
?以下是我對這兩者的理解:
首先要明白不帶緩沖的概念:所謂不帶緩沖,并不是指內(nèi)核不提供緩沖,而是只單純的系統(tǒng)調(diào)用,不是函數(shù)庫的調(diào)用。系統(tǒng)內(nèi)核對磁盤的讀寫都會提供一個(gè)塊緩沖,當(dāng)用write函數(shù)對其寫數(shù)據(jù)時(shí),直接調(diào)用系統(tǒng)調(diào)用,將數(shù)據(jù)寫入到塊緩沖進(jìn)行排隊(duì),當(dāng)塊緩沖達(dá)到一定的量時(shí),才會把數(shù)據(jù)寫入磁盤。因此所謂的不帶緩沖的I/O是指進(jìn)程不提供緩沖功能。每調(diào)用一次write或read函數(shù),直接系統(tǒng)調(diào)用。
而帶緩沖的I/O是指進(jìn)程對輸入輸出流進(jìn)行了改進(jìn),提供了一個(gè)流緩沖,當(dāng)用fwrite函數(shù)網(wǎng)磁盤寫數(shù)據(jù)時(shí),先把數(shù)據(jù)寫入流緩沖區(qū)中,當(dāng)達(dá)到一定條件,比如流緩沖區(qū)滿了,或刷新流緩沖,這時(shí)候才會把數(shù)據(jù)一次送往內(nèi)核提供的塊緩沖,再經(jīng)塊緩沖寫入磁盤。
因此,帶緩沖的I/O在往磁盤寫入相同的數(shù)據(jù)量時(shí),會比不帶緩沖的I/O調(diào)用系統(tǒng)調(diào)用的次數(shù)要少。
下面的東西是我從網(wǎng)上查到的對這兩者的理解,我覺得還是很到位的:
以下主要討論關(guān)于open,write等基本系統(tǒng)IO的帶緩沖與不帶緩沖的差別
? ? ? 帶緩存的文件操作是標(biāo)準(zhǔn)C 庫的實(shí)現(xiàn),第一次調(diào)用帶緩存的文件操作函數(shù)時(shí)標(biāo)準(zhǔn)庫會自動分配內(nèi)存并且讀出一段固定大小的內(nèi)容存儲在緩存中。所以以后每次的讀寫操作并不是針對硬盤上的文 件直接進(jìn)行的,而是針對內(nèi)存中的緩存的。何時(shí)從硬盤中讀取文件或者向硬盤中寫入文件有標(biāo)準(zhǔn)庫的機(jī)制控制。不帶緩存的文件操作通常都是系統(tǒng)提供的系統(tǒng)調(diào)用, 更加低級,直接從硬盤中讀取和寫入文件,由于IO瓶頸的原因,速度并不如意,而且原子操作需要程序員自己保證,但使用得當(dāng)?shù)脑捫什⒉徊睢A硗鈽?biāo)準(zhǔn)庫中的 帶緩存文件IO 是調(diào)用系統(tǒng)提供的不帶緩存IO實(shí)現(xiàn)的。
“術(shù)語不帶緩沖指的是每個(gè)read和write都調(diào)用嗯內(nèi)核中的一個(gè)系統(tǒng)調(diào)用。所有的磁盤I/O都要經(jīng)過內(nèi)核的塊緩沖(也稱內(nèi)核的緩沖區(qū)高速緩 存),唯一例外的是對原始磁盤設(shè)備的I/O。既然read或write的數(shù)據(jù)都要被內(nèi)核緩沖,那么術(shù)語“不帶緩沖的I/O“指的是在用戶的進(jìn)程中對這兩個(gè) 函數(shù)不會自動緩沖,每次read或write就要進(jìn)行一次系統(tǒng)調(diào)用。“--------摘自<unix環(huán)境編程>
程序中用open和write打開創(chuàng)建并把“hello world“寫入文件test.txt,相應(yīng)用fopen和fwrite操作文件test2.txt。程序執(zhí)行到open和fopen之后,sleep 15秒,這時(shí)用ls查看生成了文件沒,這時(shí)用open打開的test.txt出現(xiàn)了,但是fopen的test2.txt沒有;當(dāng)程序執(zhí)行完write和 fwrite之后,fopen的test2.txt仍然沒有出現(xiàn)(還是用ls查看),再用cat看test.txt,可以看到 “helloworld”;最后再關(guān)閉test.txt和test2.txt,這時(shí)test2.txt出現(xiàn)了,并且其內(nèi)容也是“hello world“。
?? 該例子證明了open和write是不帶緩沖的,即程序一執(zhí)行其io操作也立即執(zhí)行,不會停留在系統(tǒng)提供的緩沖里,不需等到close操作完才執(zhí)行。與之相比的fopen和fwrite則是帶緩沖的,(一般)要等到fclose操作完后才會執(zhí)行。
??
? 相關(guān)的源碼示例如下:
?#i nclude <unistd.h>
#i nclude <iostream>
#i nclude <fcntl.h>
#i nclude <string>
#i nclude <sys/types.h>
#i nclude <sys/stat.h>
using namespace std;
int main(){
?int fd;
?FILE *file;
?char *s="hello,world\n";
?if((fd=open("test.txt",O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))==-1){
? cout<<"Error open file"<<endl;
? return -1;
?}
?if((file=fopen("test2.txt","w"))==NULL){
? cout<<"Error Open File."<<endl;
? return -1;
?}
?cout<<"File has been Opened."<<endl;
?sleep(15);
?if(write(fd,s,strlen(s))<strlen(s)){
? cout<<"Write Error"<<endl;
? return -1;
?}
?if(fwrite(s,sizeof(char),strlen(s),file)<strlen(s)){
? cout<<"Write Error in 2."<<endl;
? return -1;
?}
?cout<<"After write"<<endl;
?sleep(15);
?cout<<"After sleep."<<endl;
?close(fd);
?return 0;
}
詳情請見:http://blog.csai.cn/user1/27828/archives/2007/14285.html
以 ssize_t write(int filedes, const void *buff, size_t nbytes)和size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *fp)來講講自己對unix系統(tǒng)下帶緩存的I/O和不帶緩存的I/O的區(qū)別。
?
? ? 首先要清楚一個(gè)概念,所謂的代緩存并不是指上面兩個(gè)函數(shù)的buff參數(shù),而是指unix系統(tǒng)在內(nèi)核中所設(shè)的緩沖存儲器。
? ? 當(dāng)將數(shù)據(jù)寫到文件上時(shí),內(nèi)核先將該數(shù)據(jù)寫到緩存,如果該緩存未滿,則并不將其排入輸出隊(duì)列,直到緩存寫滿或者內(nèi)核再次需要重新使用此緩存時(shí)才將其排入輸入隊(duì)列,待其到達(dá)對首,在進(jìn)行實(shí)際的I/O操作,也就是此時(shí)才把數(shù)據(jù)真正寫到磁盤,這種技術(shù)叫延遲寫。
? ? 現(xiàn)在假設(shè)內(nèi)核所設(shè)的緩存是100個(gè)字節(jié),如果你使用write,且buff的size為10,當(dāng)你要把9個(gè)同樣的buff寫到文件時(shí),你需要調(diào)用9次write,也就是9次系統(tǒng)調(diào)用,此時(shí)也并沒有寫到硬盤,如果想立即寫到硬盤,調(diào)用fsync,可以進(jìn)行實(shí)際的I/O操作。
? ? 標(biāo)準(zhǔn)I/O,也就是帶緩存的I/O采用FILE*,FILE實(shí)際上包含了為管理流所需要的所有信息:實(shí)際I/O的文件描述符,指向流緩存的指針(標(biāo)準(zhǔn)I /O緩存,由malloc分配,又稱為用戶態(tài)進(jìn)程空間的緩存,區(qū)別于內(nèi)核所設(shè)的緩存),緩存長度,當(dāng)前在緩存中的字節(jié)數(shù),出錯(cuò)標(biāo)志等,假設(shè)流緩存的長度為 50字節(jié),把以上的數(shù)據(jù)寫到文件,則只需要2次系統(tǒng)調(diào)用(fwrite調(diào)用write系統(tǒng)調(diào)用),因?yàn)橄劝褦?shù)據(jù)寫到流緩存,當(dāng)其滿以后或者調(diào)用 fflush時(shí)才填入內(nèi)核緩存,所以進(jìn)行了2次的系統(tǒng)調(diào)用write。
? ? fflush將流所有未寫的數(shù)據(jù)送入(刷新)到內(nèi)核(內(nèi)核緩沖區(qū)),fsync將所有內(nèi)核緩沖區(qū)的數(shù)據(jù)寫到文件(磁盤)。
?
? ? 不帶緩存的read和write是相對于fread/fwrite等流函數(shù)來說明的,因?yàn)?span style="line-height:normal; font-family:Helvetica">fread和fwrite是用戶函數(shù)(3),所以他們會在用戶層 進(jìn)行一次數(shù)據(jù)的緩存,而read/write是系統(tǒng)調(diào)用(2)所以他們在用戶層是沒有緩存的,所以稱read和write是無緩存的IO,其實(shí)對于內(nèi)核來 說還是進(jìn)行了緩存,不過用戶層看不到罷了。
總結(jié)
以上是生活随笔為你收集整理的Linux下的C编程实战之文件系统编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 子宫腺肌症是怎么回事啊
- 下一篇: linux 怎么把^M去掉