嵌入式Linux系統編程——文件讀寫訪問、屬性、描述符、API

Linux 的文件模型是從 Unix 的繼承而來,所以 Linux 繼承了 UNIX 本身的大部分特性,然後加以擴展,本章從 UNIX 系統接口來描述 Linux 系統結構的特性。

操作系統是通過一系列的系統調用來提供服務的,系統調用可以簡單的定義為可以被用戶程序調用的

操作系統內的函數。一般說來,我們可以藉助於系統調用來獲得更高的效率,或者訪問標準庫中沒有的某些功能。

本文主要從以下幾個方面講述嵌入式Linux系統編程文件相關的內容

1 文件與文件訪問基本概念

2 Linux 文件

3 理解文件描述符

4 文件訪問基本概念

5 文件訪問的系統調用 API

6 文件的創建、打開和關閉

7 文件讀寫

8 文件的隨機存取

9 文件的訪問權限

10 修改文件屬性

11 其他常用的 API

12 實例: fopen 和 getc 函數的實現


2.1 文件與文件訪問基本概念

2.1.1 Linux 文件

對普通計算機用戶來說,文件是存儲在永久性存儲器上的一段數據流,通常是可執行程序或者是某種

格式的數據。文件放置於文件夾,文件夾放置於某個磁盤分區中。這是從普通計算機用戶眼裡看到的文件。 但 Linux 操作系統中文件的概念,卻遠遠不侷限與此,文件是 Linux 對大多數系統資源訪問的接口。

Linux 常見的文件類型:普通文件、目錄文件、設備文件、管道文件、套接字和鏈接文件等等。

(1)普通文件(regular file),而前面提到的普通計算機用戶看到的文件,僅僅是 Linux 文件類型中的一種,我們稱之為普通文件,它們通常駐留在磁盤上的某處。普通文件按照信息存儲方式來劃分,可分為

文本文件和二進制文件:

文本文件:這類文件以文本的某種編碼(比如 ASCII 碼)形式存儲在存儲器中。它是以“行”為基本結構的一種信息組織和存儲方式。

二進制文件:這類文件以文本的二進制形式存儲在計算機中,用戶一般不能直接讀懂它們,只有通過

相應的軟件才能將其顯示出來。二進制文件一般是可執行程序、圖形、圖像、聲音等等。

例如用 ls 命令查看一個普通文件:


嵌入式Linux系統編程——文件讀寫訪問、屬性、描述符、API


嵌入式Linux系統編程——文件讀寫訪問、屬性、描述符、API

符號鏈接文件的屬性“lrwxrwxrwx”的第一個字母是“l”。

上述是 Linux 豐富的文件類型,包括除了普通文件和目錄文件之外的幾種“特殊文件”,正是由於這些“特殊文件”的存在,Linux 程序員可以按照統一的接口來實現基本文件讀寫、設備訪問、硬盤讀寫、網絡通信、系統終端,甚至內核狀態信息的訪問等等。無論是哪種類型的文件,Linux 都把他們看作是無結構的流式文件,把文件的內容看作是一系列有序的字符流。

在 Linux 操作系統中,和文件操作相關的最基本元素是:目錄結構、索引節點和文件的數據本身。

(1)目錄結構。系統中的每一個目錄都處於一定的目錄結構當中。目錄結構含有目錄中所有目錄的列表,每個目錄項都含有一個文件名稱和一個索引節點。藉助於名稱,應用程序可以訪問目錄項的內容,而索引節點提供了文件自身的信息。所以,目錄只是將文件的名稱和它的索引節點號結合在一起的一張表,目錄中每一對文件名稱和索引節點號稱為一個連接。對於一個文件來說有唯一的索引節點號與之對應,對於一個索引節點號,卻可以有多個文件名與之對應。因此,在磁盤上的同一個文件可以通過不同的路徑去訪問它。

(2)索引節點。目錄結構包含文件名稱和目錄位置等信息,而索引節點本身並不包含這些信息,因為Linux 允許使用多個文件名來引用磁盤上的同一塊數據,多個文件名都可以訪問同一個索引節點。索引節點包含了一個文件的訪問權限、文件大小、文件最後更改的時間、文件的所有者和文件相關的特殊標誌以及其他細節。

(3)文件的數據,它的存儲位置由索引節點指定。有些特殊文件,比如管道及設備文件,在硬盤上不具

有數據區域。而普通文件和目錄都擁有數據區域。

後面我們將瞭解如何獲取目錄結構信息、從索引節點獲取文件信息以及訪問文件的數據。


2.1.2 理解文件描述符

在 UNIX 類操作系統中,所有的外圍設備(包括鍵盤和顯示器)都被看作是文件系統中的文件,因此,

所有的輸入/輸出都要通過讀文件或寫文件完成。也就是說,通過一個單一的接口就可以處理外圍設備和程序之間的所有通信。

通常情況下,在讀或寫文件之前,必須先將這個意圖通知系統,該過程稱為打開文件。如果是寫一個

文件,則可能需要先創建該文件,也可能需要丟棄該文件中原先已存在的內容。系統檢查你的權力(該文件是否存在?是否有訪問它的權限?),如果一切正常,操作系統將向程序返回一個小的非負整數,該整數稱為文件描述符。任何時候對文件的輸入/輸出都是通過文件描述符標識文件。(文件描述符類似於標準庫中的文件指針或 MS-DOS 中的文件句柄。)系統負責維護已打開文件的所有信息,用戶程序只能通過文件描述符引用文件。

因為大多數的輸入/輸出是通過鍵盤和顯示器來實現的,為了方便起見,UNIX 對此做了特別的安排。

當命令解釋程序(即“shell”)運行一個程序的時候,它將打開 3 個文件,對應的文件描述符分別為 0、1、 2,依次表示標準輸入、標準輸出和標準錯誤。如果程序從文件 0 中讀,對 1 和 2 進行寫,就可以進行輸入/輸出而不必關心打開文件的問題。

程序的使用者可通過“”重定向程序的 I/O:

program <infile>outfile/<infile>

這種情況下,shell 把文件描述符 0 和 1 的默認值改為指定的文件。通常文件描述符 2 仍與顯示器相

關聯,這樣,出錯信息會輸出到顯示器上。與管道相關的輸入/輸出也有類似的特性。在任何情況下,文件賦值的改變都不是由程序完成的,而是由 shell 完成的。只要程序使用文件 0 作為輸入,文件 1 和 2作為輸出,它就不會知道程序的輸入從哪裡來,並輸出到哪裡去。


2.1.3 文件訪問基本概念

上一節我們提到和文件操作相關的最些本元素:目錄結構、索引節點和文件數據。通過 opendir()及

相關函數,可以獲取目錄結構信息。通過系統調用 stat()可以從索引節點獲得文件信息。通過常用的文件操作 open()、read()等等可以訪問文件的數據。Linux 提供了豐富的文件訪問接口,首先,我們需要了解Linux 文件訪問的實質,然後,才能靈活運用各種文件訪問函數以及系統調用進行文件操作。

在 Linux 操作系統中,每一個文件都有一個唯一的索引節點,而每個索引節點可以有一個或者多個指

向它的符號名,這些符號名就是文件的路徑名。一個文件路徑名只能指向一個索引節點(但一個索引節點可以有多個路徑名),因此文件路徑名唯一的標識單個文件。比如“/home/alex/myfile.txt”是一個文件路

徑名,指向的是“/home/alex”目錄下的 myfile.txt 文件,通過“/home/alex/myfile.txt”這個路徑名只能訪問到這個文件。

程序要訪問一個文件,首先需要通過一個文件路徑名打開文件。當進程打開一個文件的時候,進程將

獲得一個非負整數標識,即“文件描述符”。通過文件描述符,可以對文件進行 I/O 處理。

在 Linux 操作系統中各種類型的文件都採用統一的 I/0 方法來進行訪問。因此,從磁盤中讀取一個文

件中的程序和從網絡中讀取數據的程序一樣簡單,而且,程序員可以使用統一接口來訪問硬件設備和系統端口等等。

對文件執行 I/0 操作,有兩種基本方式:一種是系統調用的 I/0 方法,另一種是基於流的 I/O 方法。

系統調用的 I/O 方法提供了最基本的文件訪問接口,包括 open()、close()、write()、read()和 lseek()

等。基於流的 I/0 方法實際上是建立在系統調用的 I/0 方法基礎上的 C 函數庫,它基於系統調用方法的封裝並增加了額外的功能,例如採用緩衝技術來提高程序的效率、輸入解析以及格式化輸出等。然而在處理設備、管道、網絡套接字和其他特殊類型的文件的時候,必須使用系統調用 I/0 方法。

系統調用 I/0 方法和基於流的 I/0 方法的區別是:

(1)基於流的文件操作函數的名字都是以字母“f”開頭,而系統調用函數則不同。例如流函數 fopen()

對應於系統調用的 open()。

(2)系統調用 I/0 方法是更低一級的接口,通常完成相同的任務是,比使用基於流的 I/0 方法需要更多

編碼的工作量。

(3)系統調用直接處理文件描述符,而流函數則處理“FILE*”類型的文件句柄。

(4)基於流的 I/0 方法是對系統調用方法的封裝,流 I/0 方法使用自動緩衝技術,使程序能減少系統調

用,從而提高程序的性能。

(5)基於流的 I/0 方法可以支持格式化輸出,比如使用 fprintf()這樣的函數。

(6)基於流的 I/0 方法替用戶處理有關係統調用的細節,比如系統調用被信號中斷的處理等等。

基於流的 I/0 方法顯然給程序員提供了極大的方便,但是某些程序卻不能使用基於流的 I/0 方法。比

如使用緩衝技術使得網絡通信陷入困境,因為它將干擾網絡通訊所使用的通信協議。考慮到這兩種 I/O 方法的不同,在使用終端或者通過文件交換信息是,通常採用基於流的 I/O 方法。而使用網絡或者管道通信時,通常採用系統調用的 I/O 方法。


嵌入式Linux系統編程——文件讀寫訪問、屬性、描述符、API


嵌入式Linux系統編程——文件讀寫訪問、屬性、描述符、API


嵌入式Linux系統編程——文件讀寫訪問、屬性、描述符、API


嵌入式Linux系統編程——文件讀寫訪問、屬性、描述符、API

源文件:read_test.c

<code>#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main( int argc, char **argv ) {
\tint fd_read;
\tchar buf[100];
\tint ret;
\t/*判斷是否傳入文件名*/
\tif( argc < 2 ){
\t\tprintf("Usage: read_test FILENAME\\n");
\t\treturn -1;

\t}
\t/*打開要讀取的文件*/
\tfd_read = open( argv[1], O_RDONLY );
\t\tif( fd_read == -1 ){
\t\tperror( "open error" );
\t\treturn -1;
\t}
\t/*讀取數據*/
\tret = read( fd_read, buf, sizeof( buf ) );
\tprintf("read return = [%d]\\n", ret );
\t/*如果讀出數據,則打印數據*/
\tif( ret >= 0 ){
\t\tprintf("=========== buf ===========\\n");
\t\tprintf("%s\\n", buf );
\t\tprintf("===========================\\n");
\t}else{
\t\tperror( "read error" );
\t}
\tclose( fd_read );
\treturn 0;
}/<unistd.h>/<fcntl.h>/<stdio.h>/<code>


嵌入式Linux系統編程——文件讀寫訪問、屬性、描述符、API


嵌入式Linux系統編程——文件讀寫訪問、屬性、描述符、API


嵌入式Linux系統編程——文件讀寫訪問、屬性、描述符、API


嵌入式Linux系統編程——文件讀寫訪問、屬性、描述符、API


嵌入式Linux系統編程——文件讀寫訪問、屬性、描述符、API


嵌入式Linux系統編程——文件讀寫訪問、屬性、描述符、API


嵌入式Linux系統編程——文件讀寫訪問、屬性、描述符、API


嵌入式Linux系統編程——文件讀寫訪問、屬性、描述符、API


嵌入式Linux系統編程——文件讀寫訪問、屬性、描述符、API


嵌入式Linux系統編程——文件讀寫訪問、屬性、描述符、API


嵌入式Linux系統編程——文件讀寫訪問、屬性、描述符、API


嵌入式Linux系統編程——文件讀寫訪問、屬性、描述符、API


分享到:


相關文章: