2021-05-12 14:32:11
Linux檔案操作的主要介面API及相關細節
作業系統API:
1、API是一些函數,這些函數是由linux系統提供支援的,由應用層程式來使用,應用層程式通過呼叫API來呼叫作業系統中的各種功能,來幹活
檔案操作的一般步驟:
1、在linux系統中要操作一個檔案,一般是先open開啟一個檔案,得到一個檔案描述符,然後對檔案進行讀寫操作(或其他操作),最後close關閉檔案即可
2、檔案平時是存在塊裝置中的檔案系統中的,我們把這種檔案叫靜態檔案。當我們去open開啟一個檔案時,linux核心做的操作包括:核心在進程中建立了一個開啟檔案的資料結構,記錄下我們開啟的這個檔案;核心在記憶體中申請一段記憶體,並且將靜態檔案的內容從塊裝置中讀取到記憶體中特定地址管理存放(叫動態檔案)。開啟檔案後,以後對這個檔案的讀寫操作,都是針對記憶體中這一份動態檔案的,而並不是針對靜態檔案的。當我們對動態檔案進行讀寫後,此時記憶體中的動態檔案和塊裝置中的靜態檔案就不同步了,當我們close關閉動態檔案時,close內部核心將記憶體中的動態檔案的內容去更新(同步)塊裝置中的靜態檔案。
3、為什麼要這麼設計?
以為塊裝置本身有讀寫限制(回憶NnadFlash、SD等塊裝置的讀寫特徵),本身對塊裝置進行操作非常不靈活。而記憶體可以按位元組為單位來操作,而且可以隨機操作(記憶體就叫RAM,random),很靈活。所以核心設計檔案操作時就這麼設計了
檔案描述符:
1、檔案描述符其實實質是一個數位,這個數位在一個進程中表示一個特定的含義,當我們open開啟一個檔案時,作業系統在記憶體中構建了一些資料結構來表示這個動態檔案,然後返回給應用程式一個數位作為檔案描述符,這個數位就和我們記憶體中維護這個動態檔案的這些資料結構掛鉤繫結上了,以後我們應用程式如果要操作這一個動態檔案,只需要用這個檔案描述符進行區分。
2、檔案描述符的作用域就是當前進程,出了當前進程這個檔案描述符就沒有意義了
實時查man手冊
(1)當我們寫應用程式時,很多API原型都不可能記得,所以要實時查詢,用man手冊
(2)man 1 xx查linux shell命令,man 2 xxx查API, man 3 xxx查庫函數
讀取檔案內容
ssize_t read(int fd, void *buf, size_t count);
fd表示要讀取哪個檔案,fd一般由前面的open返回得到
buf是應用程式自己提供的一段記憶體緩衝區,用來儲存讀出的內容
count是我們要讀取的位元組數
返回值ssize_t型別是linux核心用typedef重定義的一個型別(其實就是int),返回值表示成功讀取的位元組數。
exit、_exit、_Exit退出進程
(1)當我們程式在前面步驟操作失敗導致後面的操作都沒有可能進行下去時,應該在前面的錯誤監測中結束整個程式,不應該繼續讓程式執行下去了。
(2)我們如何退出程式?
第一種;在main用return,一般原則是程式正常終止return 0,如果程式異常終止則return -1。
第一種:正式終止進程(程式)應該使用exit或者_exit或者_Exit之一
open函數的flag詳解:
讀寫許可權:O_RDONLY O_WRONLY O_RDWR
O_RDONLY就表示以唯讀方式開啟,
O_WRONLY表示以只寫方式開啟,
O_RDWR表示以可讀可寫方式開啟
當我們附帶了許可權後,開啟的檔案就只能按照這種許可權來操作
開啟存在並有內容的檔案時:O_APPEND、O_TRUNC
O_APPEND屬性去開啟檔案時,如果這個檔案中本來是有內容的,則新寫入的內容會接續到原來內容的後面;
O_TRUNC屬性去開啟檔案時,如果這個檔案中本來是有內容的,則原來的內容會被丟棄
fd = open("a.txt", O_RDWR | O_APPEND | O_TRUNC);
if (-1 == fd) // 有時候也寫成: (fd < 0)
{
printf("檔案開啟錯誤n");
// return -1;
_exit(-1);
}
else
{
printf("檔案開啟成功,fd = %d.n", fd);
}
開啟不存在的檔案時:O_CREAT、O_EXCL
open中加入O_CREAT後,不管原來這個檔案存在與否都能開啟成功,如果原來這個檔案不存在則建立一個空的新檔案,如果原來這個檔案存在則會重新建立這個檔案,原來的內容會被消除掉;
O_EXCL標誌和O_CREAT標誌結合使用時,則沒有檔案時建立檔案,有這個檔案時會報錯提醒我們;
open函數在使用O_CREAT標誌去建立檔案時,可以使用第三個引數mode來指定要建立的檔案的許可權。mode使用4個數位來指定許可權的,其中後面三個很重要,對應我們要建立的這個檔案的許可權標誌。譬如一般建立一個可讀可寫不可執行的檔案就用0666
fd = open("a.txt", O_RDWR | O_CREAT | O_EXCL, 0666);
if (-1 == fd) // 有時候也寫成: (fd < 0)
{
perror("檔案開啟錯誤");
_exit(-1);
}
else
{
printf("檔案開啟成功,fd = %d.n", fd);
}
O_NONBLOCK
(1)阻塞與非阻塞。如果一個函數是阻塞式的,則我們呼叫這個函數時當前進程有可能被卡住(阻塞住,實質是這個函數內部要完成的事情條件不具備,當前沒法做,要等待條件成熟),函數被阻塞住了就不能立刻返回;如果一個函數是非阻塞式的那麼我們呼叫這個函數後一定會立即返回,但是函數有沒有完成任務不一定。
(2)阻塞和非阻塞是兩種不同的設計思路,並沒有好壞。總的來說,阻塞式的結果有保障但是時間沒保障;非阻塞式的時間有保障但是結果沒保障。
(3)作業系統提供的API和由API封裝而成的庫函數,有很多本身就是被設計為阻塞式或者非阻塞式的,所以我們應用程度呼叫這些函數的時候心裡得非常清楚。
(4)我們開啟一個檔案預設就是阻塞式的,如果你希望以非阻塞的方式開啟檔案,則flag中要加O_NONBLOCK標誌。
(5)只用於裝置檔案,而不用於普通檔案。
errno和perror
(1)errno就是error number,意思就是錯誤號碼。linux系統中對各種常見錯誤做了個編號,當函數執行錯誤時,函數會返回一個特定的errno編號來告訴我們這個函數到底哪裡錯了。
(2)linux系統提供了一個函數perror(意思print error),perror函數內部會讀取errno並且將這個不好認的數位直接給轉成對應的錯誤資訊字串,然後print列印出來。
read和write的count
(1)count和返回值的關係:count參數列示我們想要寫或者讀的位元組數,返回值表示實際完成的要寫或者讀的位元組數。實現的有可能等於想要讀寫的,也有可能小於(說明沒完成任務)
(2)count再和阻塞非阻塞結合起來,就會更加複雜。如果一個函數是阻塞式的,則我們要讀取30個,結果暫時只有20個時就會被阻塞住,等待剩餘的10個可以讀。
(3)有時候我們寫正式程式時,我們要讀取或者寫入的是一個很龐大的檔案(譬如檔案有2MB),我們不可能把count設定為2*1024*1024,而應該去把count設定為一個合適的數位(譬如2048、4096),然後通過多次讀取來實現全部讀完。
ret = write(fd, writebuf, strlen(writebuf));
if (ret < 0)
{
//printf("write失敗.n");
perror("write失敗");
_exit(-1);
}
else
{
printf("write成功,寫入了%d個字元n", ret);
}
檔案IO效率和標準IO
(1)檔案IO就指的是open、close、write、read等API函數構成的一套用來讀寫檔案的體系,這套體系可以很好的完成檔案讀寫,但是效率並不是最高的。
(2)應用層C語言庫函數提供了一些用來做檔案讀寫的函數列表,叫標準IO。標準IO由一系列的C庫函數構成(fopen、fclose、fwrite、fread),這些標準IO函數其實是由檔案IO封裝而來的(fopen內部其實呼叫的還是open,fwrite內部還是通過write來完成檔案寫入的)。標準IO加了封裝之後主要是為了在應用層新增一個緩衝機制,這樣我們通過fwrite寫入的內容不是直接進入核心中的buf,而是先進入應用層標準IO庫自己維護的buf中,然後標準IO庫自己根據作業系統單次write的最佳count來選擇好的時機來完成write到核心中的buf(核心中的buf再根據硬碟的特性來選擇好的實際去最終寫入硬碟中)。
本文永久更新連結地址:http://www.linuxidc.com/Linux/2016-11/137389.htm
相關文章