unix高級編程系列之文件I/O

背景

作為linux 開發者,我們不可避免會接觸到文件編程。比如通過文件記錄程序配置參數,通過字符設備與外設進行通信。因此作為合格的linux開發者,一定要熟練掌握文件編程。在文件編程中,我們一般會有兩類接口函數:標準I/O(帶緩沖)和POXIS.1 I/O(不帶緩沖)。本章節主要介紹不帶緩沖的相關API及注意事項。

open 接口

open函數的作用是打開或創建一個文件。其聲明如下:

int open(const char* path, int oflag, .../*mode_t mode*/);

參數解析:

  • path :是打開或需要創建的文件名稱;
  • oflag : 設置打開文件的權限,該參數取值范圍較廣。并且需要區分。大致可以分為兩類:
  1. 權限類型標識。需要關注的有O_RDONLY(只讀權限)、O_WRONLY(只寫權限)、O_RDWR(可讀寫權限)。這三個標識位必須指定一個且只能指定一個。(O_ECEC(只執行)和O_SEARCH(只搜索)已被移除)。
  2. 特性類標識。這些標識可多選,常見的有如下:
標識常量含義
O_APPEND每次寫都追加到文件的尾端。即使你顯式的調用lseek改變文件當前偏移量,但是在write時,依然會追加到文件末尾
O_CREAT若文件不存在則創建它。
O_EXCL如果同時指定了O_CREAT,而文件已經存在,則出錯。經常用于判斷文件是否存在,與access()函數功能類似。
O_NOBLOCK如果path引用的是一個FIFO、塊特殊文件、字符特殊文件。那么本文件描述符后續的I/O操作,都設置為非阻塞方式。
O_SYNC每次write 都會等待物理I/O操作完成,包括文件屬性更新。 在linux ext4 系統中,該標識可能不生效
O_TRUNC如果文件存在,且以只寫或讀寫權限打開。則將長度截斷為0。常見的業務場景就是更新配置文件。
  • mode 可選參數。只有當oflag參數中,具備O_CREAT屬性時,才需要指定新創建的文件權限。

知識點open 函數返回的文件描述符一定時最小的未用描述符數值

基于上述知識點,經常會被用來重定向程序的標準輸入、標準輸出或標準錯誤輸出。

場景如下:有一個封裝庫內部是通過采用的是printf進行日志打印,無法體現在我們日志系統中。我們如何觀察其日志輸出呢?常見做法如下:

#include<stdlib.h>
#include<stdio.h>
int main()
{ printf("hello world\n");return 0;
}

默認情況下,日志輸出到終端:

xieyihua@xieyihua:~/test$ gcc 1.c  -o 1
xieyihua@xieyihua:~/test$ ./1
hello world
xieyihua@xieyihua:~/test$

可以做以下修改:

#include<stdlib.h>
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{close(1); /* 關閉 文件描述符1'*/open("./log",O_WRONLY|O_CREAT,0755);/* 此時文件描述符1 與 ./log文件綁定*/printf("hello world\n");return 0;
}

輸出如下:

xieyihua@xieyihua:~/test$ gcc 1.c -o 1
xieyihua@xieyihua:~/test$ ./1
xieyihua@xieyihua:~/test$ cat log
hello world
xieyihua@xieyihua:~/test$

creat 接口

creat函數主要用于創建一個新文件。函數原型聲明如下:

#include <fcntl.h>
int creat(const char* path, mode_t mode);

其等效于:open(path,O_WRONLY|O_CREAT|O_TRUNC,mode);但是由于creat只能以只寫方式打開文件,使用場景便較少,漸漸被‘冷落’了。

close 接口

close函數關閉一個打開文件。其函數原型聲明如下:

#include<unistd.h>
int close(int fd);

知識點當一個進程終止時,內核會自動關閉它所有的打開文件。很多程序都利用了這一功能而不顯示調用close

lseek 接口

每個打開文件都有一個與其相關的“當前文件偏移量”。它通常是一個非負整數(/dev/kmem/支持負的偏移量),用于度量從文件開始處計算的字節數。通常讀、寫操作都是從當前文件偏移處開始的,并使偏移量增加所讀寫的字節數。

lseek接口就可以顯式的為一個打開的文件設置偏移量。其函數原型聲明如下:

#include <unistd.h>
off_t lseek(int fd,off_t offset,int whence);
  • fd, 文件描述符
  • offset,其含義與whence 的值相關。
  1. whenceSEEK_SET,則將該文件的偏移量設置為距文件開始處的offset個字節。
  2. whenceSEEK_CUR,則將該文件的偏移量設置為當前值加上offset個字節,offset可為正或負。
  3. whenceSEEK_END,則將該文件的偏移量設置為文件長度加上offset個字節,offset可為正或負。

知識點 系統默認情況下,當打開一個文件時,除非指定O_APPEND選項,否則該偏移量被設置為0

lseek 僅是將當前的文件偏移量記錄在內核中,并不引起任何I/O操作,其目的是用于接下來的讀寫操作。

空洞文件

文件偏移量可以被設置為大于文件當前長度,在這種情況下,對該文件的下一次寫將加長該文件,并在文件中構成一個空洞。位于文件中沒有寫過的字節都被讀為0。并且文件中的空洞并不要求在磁盤上占用存儲區。示例代碼如下:

#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>int main()
{int fd;char* buf1 = "123456789a";char* buf2 = "abcdefghij";if((fd = creat("file.hole",0755)) < 0){printf("creat error\n");return -1;}printf("fd = %d\n",fd);if(write(fd,buf1,10) != 10){printf("write buf1 error\n");}if(lseek(fd,16384,SEEK_SET) == -1){printf("lseek error\n");}if(write(fd,buf2,10) != 10){printf("write buf1 error\n");}return 0;
}

結果如下:

/*文件長度有16394 Byte*/
xieyihua@xieyihua:~/test$ ls -la file.hole
-rwxr-xr-x 1 xieyihua xieyihua 16394 Jul  4 01:58 file.hole/*文本的實際內容也只有兩個字符串*/
xieyihua@xieyihua:~/test$ od -c file.hole   
0000000   1   2   3   4   5   6   7   8   9   a  \0  \0  \0  \0  \0  \0
0000020  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
*
0010000   a   b   c   d   e   f   g   h   i   j
0010012
xieyihua@xieyihua:~/test$ cat file.hole
123456789aabcdefghijxieyihua@xieyihua:~/test$/* 沒有空洞的文件其占用了16個磁盤塊*/
xieyihua@xieyihua:~/test$ ls -ls file.*8 -rwxr-xr-x 1 xieyihua xieyihua 16394 Jul  4 01:58 file.hole
16 -rwxr-xr-x 1 xieyihua xieyihua 16384 Jul  4 01:58 file.nohole

文件空洞的特性:分配了文件偏移量范圍,但是實際卻沒有分配磁盤空間。我們一般可在兩個方向應用:

  1. 多線程下載。當創建一個巨大的文件時,單個線程逐步構建文件會耗費大量時間。一種優化思路是將文件劃分為多個段,利用多線程同時操作,每個線程負責寫入其中一段數據。這類似于現實生活中修路的場景,如修建高速公路時,單個施工隊的進度可能較慢,但通過安排多個施工隊,每個隊負責修建一段,最終將它們連接起來,大大提高了效率。
  2. 共享內存。當不同進程需要共享內存時,并不清楚實際需要多大的文件,可以先開辟一個大文件。比如:在創建虛擬機時,如果一開始就分配了100GB的磁盤空間,而實際上系統安裝完成后可能只使用了3、4GB的空間,這就是空洞文件的應用。通過空洞文件,可以避免一開始就分配過多的資源,節約了存儲空間的浪費。

read 接口

read接口用于向打開文件中讀數據。其原型聲明如下:

#include<unistd.h>
ssize_t read(int fd, void* bff, size_t nbyte);

read成功,則返回讀到的字節數,如果已經達到文件的尾端,則返回0。

知識點 大多數文件系統為改善性能都會采用某種預讀技術,即即使你每次僅讀取100Byte內容,但是實際上會從磁盤中讀取一頁數據,保存在內存中。從而減少磁盤I/O操作,提高系統性能。但是也會增加內存使用壓力。

write 接口

write接口用于向打開文件寫數據。其接口聲明如下:

#include <unistd.h>
ssize_t write(int fd, const void* buf,size_t nbytes);

其返回值通常與參數nbytes的值相同,否則標識出錯。其出錯原因:

  1. 磁盤已寫滿
  2. 超過文件限定長度

linux 內核標識打開文件的方式

linux 內核通過三個數據結構表示打開的文件。記錄項、文件表項、V節點。其三者關系大致如下:

  • 進程表項中,記錄中文件描述符文件表項的關系
  • 文件表項中,記錄文件狀態標志位當前文件偏移量V節點指針
  • V節點中,記錄文件類型各種操作函數指針指向i節點。而i節點包含文件的詳細信息,比如:文件的所有者、文件長度、指向文件實際數據塊再磁盤上所在位置的指針等。

注意:每一個文件只有一個唯一的V節點表;多個文件表項可以指向同一個V節點表,每調用一次open,則創建一個新的文件表項;不同fd可以指向同一個文件表項;即可能存在以下場景:

正是這樣的機制原理,linux 讓我們可以多任務同時訪問同一文件。但是在寫文件時,我們需要關注寫入時序以及數據錯亂問題。

  • 在完成每一個readwrite操作后,文件表項中的當前文件偏移量增加所讀寫的字節數。
  • 如果使用O_APPEND標志打開一個文件,則響應標志也被設置到文件表項的文件狀態標志中。每次進行寫操作時,文件表項中的當前文件偏移量首先會被設置為表項中的文件長度。這就確保每次寫入的數據追加到當前尾端處。
  • lseek 函數只是修改文件表項中的當前文件偏移量,不進行任何I/O操作。
  • 每一個進程都有它自己的文件表項和進程表項。

dup和dup2

這兩個接口用于復制一個現有的文件描述符。其接口聲明如下:

#include<unistd.h>int dup(int fd);
int dup2(int fd, int fd2);
/*兩函數的返回值:若成功,返回新的文件描述符;若出錯,返回-1*/

dup返回新的文件描述符一定是當前可用文件描述符中的最小數值。其效果就是多個fd指向同一個文件表項。其關系與上圖中多線程訪問文件一致。

sync、fsync、fdatasync接口

傳統的linux 系統中設備緩沖區高速緩存或頁高速緩存,大多數磁盤I/O都通過緩沖區進行。當我們向文件寫入數據時,內核通常先將數據復制到緩沖區中,然后排入隊列,晚些時候在寫入磁盤。這種方式稱為“延遲寫”。

“延遲寫”雖然提高了write的響應速度(不需要等待數據經過IO,寫入磁盤)。但是也帶來了風險:當應用層認為已經將數據寫入文件了,但實際數據還并沒有落入磁盤。若此時系統出現異常,則會將這部分數據丟失。為了避免這種情況,linux 系統提供了syncfsyncfdatasync接口,應用層主動要求內核將緩沖區中的數據進行落盤。原型聲明如下:

#include<unistd.h>
int fsync(int fd);
int fdatasync(int fd);void sync(void);
  • sync 只是將所有修改過的塊緩沖區排入寫隊列,然后就返回。它并不等待實際寫磁盤操作結束
  • fsync 函數只對文件描述符fd指定的文件起作用,并等待寫磁盤操作結束才返回
  • fdatasync 函數類似于 fsync,但只影響文件的數據部分。

注: open 接口中有一個標識 O_SYNC含義標識同步寫,但經過驗證,似乎并不起作用,與預期不一致。建議為了保險起見,還是調用fsync接口。

總結

文件編程是Linux開發者必須掌握的技能。本文介紹了Linux文件編程中常用的API及其注意事項,包括open、creat、close、lseek、read、write、dup和dup2等。還介紹了sync、fsync和fdatasync等接口,用于確保數據安全。此外,文章還解釋了Linux內核如何標識打開的文件,以及文件表項、V節點和進程表項之間的關系。希望能給您帶來幫助。

若我的內容對您有所幫助,還請關注我的公眾號。不定期分享干活,剖析案例,也可以一起討論分享。
我的宗旨:
踩完您工作中的所有坑并分享給您,讓你的工作無bug,人生盡是坦途

在這里插入圖片描述

參考文章:https://applink.feishu.cn/client/message/link/open?token=AmX27V1AQAADZjdT9KRAgAQ%3D

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/41767.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/41767.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/41767.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Mysql慢日志、慢SQL

慢查詢日志 查看執行慢的SQL語句&#xff0c;需要先開啟慢查詢日志。 MySQL 的慢查詢日志&#xff0c;記錄在 MySQL 中響應時間超過閥值的語句&#xff08;具體指運行時間超過 long_query_time 值的SQL。long_query_time 的默認值為10&#xff0c;意思是運行10秒以上(不含10秒…

實現基于Spring Boot的Web安全防護

實現基于Spring Boot的Web安全防護 大家好&#xff0c;我是免費搭建查券返利機器人省錢賺傭金就用微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01; 在當今互聯網應用的開發中&#xff0c;保護用戶數據和系統安全至關重要。S…

阿里云RDS云數據庫庫表恢復操作

最近數據庫中數據被人誤刪了,記錄一下恢復操作方便以后發生時進行恢復. 1.打開控制臺&#xff0c;進入云數據庫實例. 2.進入實例后 &#xff0c;點擊右側的備份恢復&#xff0c;然后看一下備份時間點&#xff0c;中間這邊都是阿里云自動備份的備份集&#xff0c;基本都是7天一備…

詳解「一本通 5.1 練習 1」括號配對(區間DP經典題)

一.題目 二.思路 題目的大意是說:給你一個只由[ ] ( )構成的字符串&#xff0c;請問需要增加多少個字符才能使其變為一個合法的括號序列。 因為添加若干字符使其達到匹配的目的等價于將不匹配的字符去除使得字符串達到匹配的目的 所以這題只需計算出已匹配完成的括號數,再…

中英雙語介紹倫敦金融城(City of London)

中文版 倫敦金融城&#xff0c;通常稱為“金融城”或“城”&#xff08;The City&#xff09;&#xff0c;是英國倫敦市中心的一個著名金融區&#xff0c;具有悠久的歷史和全球性的影響力。以下是關于倫敦金融城的詳細介紹&#xff0c;包括其地理位置、人口、主要公司、歷史背…

機器學習原理之 -- 隨機森林分類:由來及原理詳解

隨機森林分類器是機器學習中一種強大且靈活的集成學習方法。它通過構建多棵決策樹并結合其結果來提高分類精度和穩定性。本文將詳細介紹隨機森林分類器的由來、基本原理、構建過程及其優缺點。 二、隨機森林的由來 隨機森林&#xff08;Random Forest&#xff09;由Leo Breima…

【優化論】約束優化算法

約束優化算法是一類專門處理目標函數在存在約束條件下求解最優解的方法。為了更好地理解約束優化算法&#xff0c;我們需要了解一些核心概念和基本方法。 約束優化的核心概念 可行域&#xff08;Feasible Region&#xff09;&#xff1a; 比喻&#xff1a;想象你在一個園藝場…

基于機器學習的永磁同步電機矢量控制策略-高分資源-下載可用!

基于機器學習的永磁同步電機矢量控制策略 優勢 訓練了RL-Agent&#xff0c;能夠提高電機在非線性負載下的性能。 部分程序 仿真結果 轉矩估計及dq軸電流。 代碼有償&#xff0c;50&#xff0c;需要的可以聯系。

數學建模算法目標規劃

在人們的生產實踐中&#xff0c;經常會遇到如何利用現有資源來安排生產&#xff0c;以取得最大經濟 效益的問題。此類問題構成了運籌學的一個重要分支—數學規劃&#xff0c;而線性規劃(Linear Programming 簡記 LP)則是數學規劃的一個重要分支。特別是在計算機能處理成千上萬個…

底層軟件 | STM32啟動分析之main函數是怎樣跑起來的

應屆生面試&#xff0c;基本上嵌入式一般都是基于32的項目&#xff0c;記得我當年面大疆的就是有這個題目。 1、STM32啟動規則 STM32根據boot0和boot1的電平決定啟動位置&#xff0c;boot00時從主Flash啟動&#xff0c;即0x08000000地址啟動。 按照spec&#xff0c;M3核的中斷…

構建工程化:多種不同的工程體系如何編寫MakeFile

源碼分析 核心MakeFile 這個 Makefile 是一個復雜的構建腳本&#xff0c;用于管理和構建一個大型項目。它包括多個目標、條件判斷和遞歸調用 make 命令來處理多個子項目和子目錄。讓我們逐部分進行詳細解析。 偽目標和變量定義 .PHONY: all clean install build test init.…

依賴注入的優點、解決的問題以及其底層原理和邏輯

依賴注入&#xff08;Dependency Injection, DI&#xff09;是一種設計模式&#xff0c;用于實現控制反轉&#xff08;Inversion of Control, IoC&#xff09;。它通過將對象的依賴關系從類內部轉移到外部配置或注入&#xff0c;從而提高代碼的可維護性、可測試性和可擴展性。以…

使用Spring Boot和Apache Camel集成第三方服務

使用Spring Boot和Apache Camel集成第三方服務 大家好&#xff0c;我是免費搭建查券返利機器人省錢賺傭金就用微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01;今天我們將探討如何利用Spring Boot和Apache Camel來集成第三方服…

pycharm如何使用jupyter

目錄 配置jupyter新建jupyter文件別人寫的方法&#xff08;在pycharm種安裝&#xff0c;在網頁中使用&#xff09; pycharm專業版 配置jupyter 在pycharm終端啟動一個conda虛擬環境&#xff0c;輸入 conda install jupyter會有很多前置包需要安裝&#xff1a; 新建jupyter…

一文理清LK光流

舉出幾種光流方法&#xff0c;說明LK光流的建模方式&#xff1f; 光流方法是用于估計圖像序列中像素點運動的技術&#xff0c;廣泛應用于計算機視覺和視頻處理領域。以下是幾種常見的光流方法&#xff1a; Lucas-Kanade (LK) 方法&#xff1a; 一種基于局部窗口的光流估計方法…

代理IP在未來將面臨哪些挑戰?

今天我們來聊聊代理IP在未來可能會面臨的挑戰。雖然代理IP技術目前應用廣泛&#xff0c;但隨著科技的發展和網絡環境的變化&#xff0c;代理IP也將面臨一些新的挑戰。讓我們一起來看看這些挑戰是什么吧&#xff01; 1. 更嚴格的網絡封鎖和檢測 現代社會各行各業都在飛速發展&…

可變參數 Collections 不可變集合 Stream流

目錄 1.可變參數&#xff1a; 2.Collections: 3.不可變集合&#xff1a; 4.Stream流: 1、什么是流 2、如何生成流 1.單列集合獲取Stream流 2.雙列集合獲取Stream流 3.數組獲取Stream流&#xff1a; 4.一堆零散數據&#xff1a; Stream接口中的靜態方法 3.Stream流的…

解決分布式環境下session共享問題

在分布式環境下&#xff0c;session會存在兩個問題 第一個問題:不同域名下&#xff0c;瀏覽器存儲的jsessionid是沒有存儲的。比如登錄時認證服務auth.gulimall.com存儲了session&#xff0c;但是搜索服務search.gulimall.com是沒有這個session的&#xff1b; 第二個問題&…

基于SpringBoot的校園臺球廳人員與設備管理系統

本系統是要設計一個校園臺球廳人員與設備管理系統&#xff0c;這個系統能夠滿足校園臺球廳人員與設備的管理及用戶的校園臺球廳人員與設備管理功能。系統的主要功能包括首頁、個人中心、用戶管理、會員賬號管理、會員充值管理、球桌信息管理、會員預約管理、普通預約管理、留言…

【SSRF】

SSRF &#xff08;Server-Side Request Forgery 服務端請求偽造&#xff09; 文章目錄 0x01 是什么&#xff1f;0x02 怎么判斷是否存在SSRF漏洞&#xff1f;0x03 防御0x04 繞過手段 0x01 是什么&#xff1f; 是什么&#xff1f; ??答&#xff1a;攻擊者構造請求&#xff0c;…