Linux操作系統-標準IO庫(1)

Linux操作系統—標準I/O庫(1)(2015-8-3)

分類:Linux操作系統

  不僅在linux,在很多操作系統上都實現了標準I/O庫,該庫由ANSI C標準說明。標準I/O庫是在系統調用函數基礎上構造的,它處理很多細節(例如緩存分配)以優化執行I/O。與基於文件描述符的I/O相比,基於流的I/O更加簡單,方便,也更加高效。因而在Linux環境C程序的編寫中,基於流的I/O使用更爲廣泛。

流和文件指針

  在基於文件描述符中的底層系統調用的I/O操作中,所有的I/O函數都是針對文件描述符的。當打開一個文件時,即返回一個文件描述符,然後該問價描述符就用於後續的I/O操作。
  在標準I/O庫中,所有的I/O操作都是圍繞流(stream)來進行的。在Linux中,文件和設備都可以看做是數據流。
  什麼是數據流?數據流是指無結構的字節序列。當用標準I/O庫打開或創建一個文件時,即將一個流與一個文件結合起來。
  標準I/O庫提供了函數fopen用於打開一個流。當打開一個流時,該函數會返回一個指向FILE對象的指針,即文件指針(類型爲FILE *)。FILE對象通常是一個結構,它包含了I/O庫爲管理該流所需要的所有信息:用於實際I/O的文件描述符,指向流緩存的指針,緩存長度,當前在緩存中的字節數,出錯標誌等。但一般應用程序沒有必要關心FILE結構體的各成員值,使用流時,只需要將FILE指針作爲參數傳遞給每個標準I/O函數。
  Linux對一個進程預定義了三個流:標準輸入流,標準輸出流和標準錯誤輸出流,它們自動地爲進程打開並可用。這三個標準I/O流通過在頭文件<stdio.h>中預定義的文件指針stdin,stdout和stderr加以引用。它們與文件描述符STDIN_FILENO,STDOUT_FILENO和STDERR_FILENO所表示的標準輸入,標準輸出和標準錯誤輸出相對應。

緩存

  爲什麼要有緩存?
  標準I/O庫(注意:標準I/O庫是在系統調用函數基礎上構造的)提供緩存的目的是儘可能減少使用read和write調用的次數,以提高I/O效率。標準I/O也對每個I/O流自動進行緩存管理,免除了由應用程序考慮這一點的麻煩。
  標準I/O提供了三種類型的緩存。
- 全緩存

  使用全緩存時,只有當標準I/O緩存填滿後才進行實際的I/O操作。
  對磁盤文件的標準I/O操作一般是實施全緩存的。在一個流上執行第一次I/O操作時,相關標準I/O函數通常調用malloc函數分配所需要使用的緩存。
  術語刷新(flush)用於說明標準I/O緩存的寫操作。緩存可由標準I/O例程自動刷新(比如當填滿一個緩存時),或者可以調用函數fflush顯示刷新一個流。

  • 行緩存

  使用行緩存時,標準I/O庫會在輸入和輸出中遇到換行符時執行實際的I/O操作。當流涉及一個終端時(例如標準輸入和標準輸出),典型地使用行緩存。
  對行緩存有兩個限制:
1. 因爲行的緩存長度是固定的,所以只要填滿了緩存,即使還沒有寫一個換行符,也會進行I/O操作。
2. 任何時候只要通過標準I/O庫要求從一個不帶緩存的流或一個行緩存的流(它預先要求從內核中得到數據,所需要的數據可能已在該緩存中)得到輸入的數據,那麼就會造成刷新所有的行緩存輸出流。

  • 不帶緩存

  即不對字符進行緩存。如果用標準I/O函數寫若干條字符到不帶緩存的流中,則相當於用write系統調用函數將這些字符寫至相關聯的打開文件上。標準錯誤輸出流stderr通常是不帶緩存的,這就使得出錯信息可以儘快顯示出來,而不管它們是否含有一個新行字符。

ANSI C規定了下列緩存特徵:
- 當且僅當標準輸入和標準輸出並不涉及交互作用設備時,它們纔是全緩存的。
- 標準錯誤輸出絕對不會是全緩存的。

流的打開和關閉

  標準I/O庫提供了fopen系列函數用以創建或打開流文件,提供了fclose函數關閉已打開的流文件。

打開流

  使用fopen系列函數可以創建或打開流文件,這些函數的原型如下。

#include <stdio.h>

FILE *fopen(const char *path, const char *mode);
FILE *fdopen(int fd, const char *mode);
FILE *freopen(const char *path, const char *mode, FILE *stream);

  這三個函數的區別如下:
(1)fopen打開路徑名由path指定的一個文件。
(2)freopen在由stream指定的流上打開一個指定的文件(其路徑由path指定),如若該流已經打開,則先關閉該流。次函數一般用於將一個指定的文件打開爲一個預定義的標準流:標準輸入,標準輸出或者標準錯誤輸出。
(3)fdopen取一個現存的文件描述符,並使一個標準的I/O流與該描述相結合。此函數常用於創建管道和網絡通信通道函數獲得的描述符。因爲這些特殊類型的文件不能用標準I/O函數fopen打開,而必須先調用設備專用函數以獲得一個文件描述符,然後用fdopen使一個標準I/O流與該描述符相結合。注意:fdopen函數不是ANSI C的標準函數,而是屬於POSIX.1的標準。
  這三個函數各參數和返回值的含義如下:
1. path:要打開或創建的文件的名字
2. mode:對該I/O流的讀,寫方式,ANSI C規定了15種不同的可能值

  • r或rb:以讀方式打開
  • w或wb:以寫方式打開或創建,並將文件長度截爲0
  • a或ab:以寫方式打開,新內容追加在文件尾
  • r+或r+b或rb+:以更新方式打開(讀和寫)
  • w+或w+b或wb+:以更新方式打開,並將文件長度截爲0
  • a+或a+b或ab+:以更新方式打開,新內容追加在文件尾

  注意:(1)字符b的作用是區分文本文件和二進制文件。但Linux內核並不對這兩種文件進行區分,所以在Linux系統環境下字符b作爲mode的一部分實際上並無作用。(2)對於fdopen,因爲該描述符已經被打開,即所引用的文件必已存在,所以fdopen以寫方式打開並不會創建該文件或截短該文件。
3-fd:待關聯的底層文件描述符
4-stream:待關聯的流的文件指針,若該流已經打開則會被先關閉
5-返回值:成功時返回流文件的指針,失敗時返回NULL
  能否成功打開文件流受打開方式的限制,如下表所示。另外,進程可打開的文件流的數量有一個上限,該上限值由stdio.h中定義的FOPEN_MAX常量定義。
  打開一個I/O流的六種不同方式的限制

限制 r w a r+ w+ a+
文件必須已存在
擦除文件以前的內容
流可讀
流可寫
流只可在尾端寫

  在指定w或a類型創建一個新文件時,無法指定該文件的存取許可權位,POSIX.1要求這種方式創建的文件具有以下存取許可權。
S_USR | S_WUSR | S_RGRP | S_WGRP | S_ROTH | S_WOTH
  除非流引用終端設備,否則系統默認它被打開時是全緩存的。若流引用終端設備,則該流四行緩存的。

關閉流

  在使用完流文件後,應調用fclose函數關閉流。fclose函數原型如下:

#include <stdio.h>
int fclose(FILE *fp);

  函數成功時返回0,失敗時返回EOF(-1)
  在文件流被關閉之前,fclose會刷新緩存中的輸出數據,但緩存中的輸入數據將被丟棄。如果標準I/O庫已經爲該流自動分配了一個緩存,則釋放此緩存。
  當一個進程正常終止時(直接調用exit函數,或從main函數返回),則所有帶未寫緩存數據的標準I/O流都被刷新,所有打開的標準I/O流都被關閉。

實踐篇

   目標一:在當前目錄下創建一個file.txt文件
方法一:

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

int main(int argc, char *argv[])
{
    FILE *fp = NULL;
    fp = fopen("file.txt", "w");

    if (fp == NULL){
        printf("Cannot create files!\n");
        exit(-1);
    }
    fclose(fp);

    return 0;
}

  該程序能夠達到預定目標,它在我的機器上的運行結果如下:

biantiao@lazybone1994-ThinkPad-E430:~/桌面$ gedit fopen.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ gcc -o fopen fopen.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ./fopen
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ls
file.txt  fopen  fopen.c  fopen.c~
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ll file.txt
-rw-rw-r-- 1 biantiao biantiao 0  84 08:57 file.txt
biantiao@lazybone1994-ThinkPad-E430:~/桌面$

運行結束後,它在當前目錄下創建了一個空的file.txt文件。

方法二:

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

int main(int argc, char *argv[])
{
    FILE *fp = NULL;
    fp = fopen("file.txt", "r");

    if (fp == NULL){
        printf("Cannot create files!\n");
        exit(-1);
    }
    fclose(fp);

    return 0;
}

  該方法和上面的區別是,fopen中的參數由w換成了r,結果怎樣呢?結果是不行的,它在我的機器上的運行結果如下:

biantiao@lazybone1994-ThinkPad-E430:~/桌面$ gedit fopen.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ gcc -o fopen fopen.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ls
fopen  fopen.c  fopen.c~
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ./fopen
Cannot create files!
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ll
總用量 28
drwxr-xr-x  2 biantiao biantiao 4096  84 09:04 ./
drwxr-xr-x 46 biantiao biantiao 4096  83 21:41 ../
-rwxrwxr-x  1 biantiao biantiao 8704  84 09:04 fopen*
-rw-rw-r--  1 biantiao biantiao  220  84 09:01 fopen.c
-rw-rw-r--  1 biantiao biantiao  220  84 08:56 fopen.c~
biantiao@lazybone1994-ThinkPad-E430:~/桌面$

  應該能夠明白創建文件該用哪個參數了吧。注意w和r的區別,如下:

r或rb:以讀的方式打開(注意只能打開,不能創建)
w或wb:以寫方式打開或創建,並將文件長度截爲0

  寫到這裏我又想寫一個小程序來驗證上面的話,真心不好意思,初學者好奇心很重。說幹就幹。驗證什麼呢?驗證以w方式打開一個文件能將其長度截爲0。首先先在當前目錄創建一個test.txt文件,在裏面輸入一句hello world。如下圖:
這裏寫圖片描述
  編寫下列程序,保存爲cut.c

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

int main(int argc, char *argv[])
{
    FILE *fp = NULL;
    fp = fopen("test.txt", "w");

    if (fp == NULL){
        printf("Open file failed!\n");
        exit(-1);
    }
    fclose(fp);

    return 0;
}

  從上面的程序可以看出,代碼只是將已存在的test.txt文件打開,然後關閉。看看test.txt文件會有什麼變化。原先test.txt文件中寫有一句Hello World!。編譯並運行,查看結果。

biantiao@lazybone1994-ThinkPad-E430:~/桌面$ gcc -o cut cut.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ls
cut  cut.c  test.txt
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ./cut
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ cat test.txt
biantiao@lazybone1994-ThinkPad-E430:~/桌面$

  從命令行中cat的結果來看,test.txt文件確實被截短爲0了。爲了更直觀我用gedit打開看看。如下圖:
這裏寫圖片描述
文件確實被截短爲0了!(下次編程時要注意了,如果不想文件原來的內容被截掉,不能使用w參數,而得使用a或r參數來實現)

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