Linux系統:詳解文件描述符與重定向原理以及相關接口(open,read,write,dup2)

本節重點?

  • 從狹義與廣義角度理解文件
  • 理解文件描述符
  • 掌握open,write,read系統調用
  • 理解重定向的概念與原理
  • 掌握重定向的指令操作
  • stdout與stderr的比較
  • 為什么存在stderr?

一、理解“文件”

1.1 狹義角度

在狹義層面,Linux文件是磁盤或存儲設備上連續或分散的數據塊集合,具有明確的元數據(如文件名、權限、所有者等),通過文件系統進行管理。其核心特征包括:

1.1.1 數據存儲載體

  • 文本文件(如.txt.conf):存儲人類可讀字符。
  • 二進制文件(如.exe.o):編譯后的程序或庫文件,需特定程序解析。
  • 設備文件(如/dev/sda/dev/null):通過文件接口與硬件或內核交互(如/dev/null丟棄所有寫入數據)。

1.1.2 元數據

每個文件由inode(索引節點)描述,包含:

  • 文件類型(普通文件、目錄、符號鏈接等)
  • 權限(rwx)與所有者(UID/GID)
  • 時間戳(創建、修改、訪問時間)
  • 實際數據塊的磁盤地址(通過直接/間接指針)。

1.2 廣義角度

在廣義層面,Linux將幾乎所有系統資源抽象為文件,通過統一的文件操作接口(open、write、read等)訪問,形成“一切皆文件”的設計哲學。

1.3 系統角度

用戶對文件的操作本質是進程對文件的操作,文件的管理者是操作系統,對文件的操作是通過文件相關的系統調用接口來實現的。

二、回顧C語言文件接口

https://blog.csdn.net/yue_2899799318/article/details/146305837?fromshare=blogdetail&sharetype=blogdetail&sharerId=146305837&sharerefer=PC&sharesource=yue_2899799318&sharefrom=from_link

三、文件相關系統調用

3.1、open

在Linux系統中系統調用open是文件操作的核心接口,它用來打開或創建文件并返回文件描述符,后續可通過文件描述符對文件進行讀寫等操作。

函數原型:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

兩參數模式:用于打開已經存在的文件,pathname是文件路徑,flags是打開方式

三參數模式:創建并打開新文件時使用,pathname是文件路徑,flags是打開方式,mode用來設置新文件創建時的權限。

參數解析 :

pathname:

要打開或創建的文件路徑,可以是絕對路徑也可以是相對路徑

flags:

必選標志:(只能選其一)

  • O_RDONLY,只讀打開
  • O_WRONLY,只寫打開
  • O_RDWR,讀寫打開

可選標志:(可組合使用)

  • O_CREAT:若文件不存在則創建,需配合mode參數。
  • O_NOFOLLOW:不跟隨符號鏈接。
  • O_DIRECTORY:要求路徑必須是目錄,否則失敗。
  • O_CLOEXEC:執行exec時自動關閉文件描述符。
  • O_SYNC:同步寫入,確保數據寫入物理設備。
  • O_NONBLOCK:非阻塞模式打開,適用于設備文件或管道。
  • O_APPEND:追加寫入,每次寫操作從文件末尾開始。
  • O_TRUNC:若文件存在且以寫模式打開,則將其長度截斷為0。
  • O_EXCL:與O_CREAT一起使用時,若文件已存在則返回錯誤,確保原子性創建。

mode:

使用mode參數時說明進程想要創建并打開一個新文件,此時mode表示創建文件時初始化文件權限。具體如下:

注意:mode參數只有O_CREAT參數被指定時有效,用來設置新文件的權限

常用權限宏(定義在<sys/stat.h>中):

  • S_IRUSR(用戶讀權限)、S_IWUSR(用戶寫權限)、S_IXUSR(用戶執行權限)。
  • S_IRGRP(組讀權限)、S_IWGRP(組寫權限)、S_IXGRP(組執行權限)。
  • S_IROTH(其他用戶讀權限)、S_IWOTH(其他用戶寫權限)、S_IXOTH(其他用戶執行權限)。

實際上由于文件掩碼的存在,文件實際的權限=mode&~umask

返回值:

成功時:返回文件描述符(非負整數)

失敗時:返回-1,并設置全局變量errno指示錯誤類型

代碼演示:

#include<stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include<string.h>
#include<errno.h>
int main()
{umask(0);int ret=open("./text.txt",O_WRONLY|O_CREAT,0666);if(ret==-1){perror("open fail!\n");printf("%s\n",strerror(errno));return errno;}printf("文件描述符為%d\n",ret);return 0;
}

3.2、write

在Linux系統中,write系統調用用來向文件描述符所指定的文件中寫入數據。

函數原型:

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

參數解析:

fd:文件描述符,通過open等系統調用獲取,標識要讀取的文件,管道,套接字等

buf:用戶空間緩沖區指針,存儲待寫入的數據。

count:請求寫入的字節數

返回值:

成功時:返回實際寫入的字節數,可能會小于count。

失敗時:返回-1,并設置全局變量errno指示錯誤類型。

代碼示例:

向指定文件中寫入字符串并讀取打印

#include<stdio.h>
#include <sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include <fcntl.h>
#include<string.h>
#include<errno.h>
int main()
{//讀寫方式打開方便我們將寫入數據后打印出來int ret=open("./text.txt",O_RDWR);if(ret==-1){printf("open fail! %s\n",strerror(errno));return 1;}//打開成功:char buff[]={"jinnzhiqi yuejianhua"};int n=write(ret,buff,sizeof(buff));if(n==-1){printf("write fail! %s\n",strerror(errno));return 2;}printf("寫入數據成功!\n"); lseek(ret,0,SEEK_SET);char buff1[1024];int sz=read(ret,buff1,sizeof(buff1)-1);buff1[sz]='\0';printf("%s\n",buff1);return 0;
}

3.3、read

在Linux系統中,系統調用read表示從文件描述符所指定的文件中讀取數據。

函數原型:

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

參數解析 :

fd:文件描述符,通過open等系統調用獲取,標識要讀取的文件,管道,套接字等。

buf:用戶空間緩沖區指針,用來存儲讀取到的數據。

count:請求讀取的最大字節數。

返回值:

成功時:返回實際讀取到的字節數。

  • 若返回值小于cout,說明數據不足read已經讀到文件末尾
  • 若返回值等于0,表示已經讀到文件末尾或連接失敗

失敗時:返回-1,并設置全局變量errno指示錯誤類型。?

代碼示例:

從指定文件中讀取字符串:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<errno.h>
int main()
{int ret=open("./text.txt",O_RDONLY);if(ret==-1){printf("open fail! %s\n",strerror(errno));return 1;}//打開成功:char buff[1024];int n=read(ret,buff,sizeof(buff)-1);if(n==-1){printf("read fail! %s\n",strerror(errno));return 2;}//讀取成功:buff[n]='\0';printf("%s\n",buff);return 0;
}

??

四、文件描述符

在Linux系統中,文件描述符(File Descriptor,簡稱FD)是操作系統內核為每個進程維護的一個非負整數標識符,用于抽象地引用進程已打開的文件、套接字(Socket)、管道(Pipe)、設備文件等I/O資源。它是進程與內核交互時管理I/O操作的核心機制。

4.1 核心概念

文件描述符是一個索引值,指向進程打開文件表(Open File Table)中的條目,而非直接指向文件本身。每個描述符對應一個內核維護的struct file結構體,記錄文件的元數據(如偏移量、權限、引用計數等)。

4.2 理解文件描述符

與進程管理類似,Linux系統對已經打開的文件也采取“先描述再組織”的管理方法。當用戶(進程)打開磁盤上的文件時,系統在系統層面會創建一個struct file結構體用來描述所打開的文件并存儲相關文件信息。

在系統層面,當有多個文件被打開時,為了更高效地管理各個已打開的文件,系統會將每個struct file結構體用雙鏈表的方式鏈接起來,此時對文件的管理就成了對該雙鏈表的增刪查改。

我們知道,Linux系統天然支持多進程,當多個進程打開多個文件時,一方面系統會給每個打開的文件創建struct file結構體并鏈入到全局鏈表中,另一方面,每個進程PCB中都會管理和維護一張文件描述符表(本質是以struct file* 為元素的指針數組)用來指明當前進程打開了多少個文件。

所以本質上,每個進程都有自己的文件描述符表(指針數組),文件描述符就是數組下標。

4.3 文件描述符的分配機制

4.3.1 分配流程

查找最小可用FD:

當進程調用open等系統調用時,內核會從進程的文件描述符表(File Descriptor Table)中搜索一個最小的未被占用的整數作為新描述符。

初始化描述符條目:

內核將該FD將一個內核維護的文件對象(struct file)進行關聯,記錄文件操作指針、偏移量、權限標志等信息。

4.3.2 關鍵數據結構

進程級文件描述符表:

每個進程都管理或維護一個獨立的FD表,由用戶態的int fd索引到內核態的struct file對象。

系統級打開文件表:

所有進程共享的全局表,存儲struct file的引用計數,inode指針等,避免重復加載文件元數據。

4.4 分配規則的核心邏輯

在Linux系統中文件描述符總是默認從低到高順序分配,也就是說內核默認優先分配最小的可用的FD,例如,到當前進程打開了FD:0、1、2則下一個分配的文件描述符就是3。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>int main()
{// 分別打印三個標準流的文件描述符printf("stdin: %d\n", stdin->_fileno);printf("stdout: %d\n", stdout->_fileno);printf("stderr: %d\n", stderr->_fileno);umask(0);int n = open("./text.txt", O_RDONLY |O_CREAT,0666);printf("open: %d\n",n);return 0;
}

?

五、 重定向

5.1 概念

在Linux系統中,文件重定向是用于控制程序輸入/輸出(I/O)流向的核心機制,允許用戶將命令的標準輸入(stdin)、標準輸出(stdout)或標準錯誤(stderr重新定向到文件、設備或其他進程,而非默認的終端(鍵盤/屏幕)

5.2 重定向的類型與語法

5.2.1 輸出重定向

>:覆蓋目標文件(若文件已存在則清空)

$ echo "Hello" > output.txt  # 將"Hello"寫入output.txt(覆蓋原有內容)

>>:追加內容到目標文件

$ echo "World" >> output.txt # 在output.txt末尾追加"World"

5.2.2 輸入重定向

<:從文件讀取并輸入(替代鍵盤輸入)

$ wc -l < input.txt  # 統計input.txt的行數(等價于wc -l input.txt)

5.2.3 錯誤重定向

2>:將標準錯誤輸出到文件(覆蓋)

$ ls /nonexistent 2> error.log  # 將錯誤信息寫入error.log

2>>:將標準錯誤追加到文件

$ ls /nonexistent1 /nonexistent2 2>> error.log  # 追加多個錯誤

5.3?底層原理(dup2系統調用)

5.3.1 dup2

dup2是Linux系統中的一個核心系統調用,用于復制文件描述符。其核心作用是將一個現有的文件描述符(oldfd)復制到指定的目標文件描述符(newfd),使newfd指向與oldfd相同的文件表項。這一機制是文件重定向、進程間通信(如管道)等操作的基礎

函數原型:

#include <unistd.h>
int dup2(int oldfd, int newfd);

?參數解析:

oldfd:需要復制的源文件描述符

newfd:目標文件描述符,若newfd已被占用,dup2會先關閉它

返回值:

成功時:返回newfd

失敗時:返回-1,并設置全局變量errno指明錯誤原因

特殊情況:

  • 若newfd與oldfd相同,則dup2會直接返回newfd不會關閉它
  • 如果oldfd無效則dup2會直接返回-1,并設置errno為EBADF

代碼演示:

?輸出重定向:

#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{umask(0);int fd=open("./text.txt",O_CREAT|O_WRONLY,0666);if(fd<0){perror("open fail!\n");return 1;}int newfd=dup2(fd,1);if(newfd<0){perror("dup2 fail!\n");return 2;}printf("hello world!\n");printf("hello world!\n");printf("hello world!\n");printf("hello world!\n");return 0;
}

5.4 stdout與stderr

5.4.1 重定向

標準輸出流(stdout)與標準錯誤流(stderror)都是進程啟動時默認打開的I/O流,屬于Unix/Linux系統的標準文件描述符(0=stdin, 1=stdout, 2=stderr)。

若為顯式重定向,兩者均會輸出到當前終端(如命令行界面)。

#include<iostream>
#include<cstdio>int main()
{    std::cout<<"hello cout"<<std::endl;std::cerr<<"hello error"<<std::endl;fprintf(stderr,"hello error\n");return 0;
}

?

?若進行重定向:

./code 1> text.txt //或者./code > text.txt

此時會發現stdout的內容會寫入文件,而strerr的內容會仍然顯式在終端

?如果想讓stderr的內容也重定向到文件text.txt中可以使用以下指令:

//將stdout重定向到text.txt后再追加stderr中的內容
./code 1> text.txt 2>>text.txt
./code 1> text.txt 2>&1

維度標準輸出(stdout)標準錯誤(stderr)
設計目的輸出程序的正常結果(如計算結果、用戶提示)。輸出程序的錯誤信息(如語法錯誤、運行時異常)。
默認行為與標準輸入(stdin)關聯,通常輸出到終端或文件。與標準輸入/輸出獨立,默認也輸出到終端,但可重定向。
緩沖機制通常是行緩沖(遇到換行符或緩沖區滿時刷新)。無緩沖立即刷新,確保錯誤信息及時顯示。
重定向方式使用?>?或?1>?重定向到文件(如?command > file)。使用?2>?或?&>?重定向到文件(如?command 2> error.log)。
文件描述符默認文件描述符為?1默認文件描述符為?2
典型內容程序運行后的正常輸出(如?echo "Hello")。程序異常時的警告或錯誤(如?ls /nonexistent)。

?5.4.2 為什么要存在stderr?

stderror是工程化設計的必然選擇:

  • 錯誤隔離:將異常信息與正常數據分離,提升系統可維護性。
  • 實時響應:無緩沖機制確保關鍵錯誤即時暴露。
  • 靈活控制:通過重定向和管道實現精細化的輸出管理。

如果沒有stderr導致無論是正常信息還是異常信息都會通過stdout來進行輸出,就會導致嚴重錯誤:

  • 用戶可能因錯誤信息被截斷或延遲而困惑,甚至無法感知程序失敗。
  • 監控腳本無法區分正常數據與錯誤,導致誤報或漏報

為了區分兩者我們必須花費大量時間來過濾信息,這樣做低效且會增加代碼復雜度。

而通過系統級機制stderr將異常信息與正常數據分離,可以提升系統可維護性,也可以通過重定向和管道實現精細化的輸出管理。

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

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

相關文章

美國市場變局:沃爾瑪95%覆蓋率的3個流量入口重構策略

過去幾年&#xff0c;美國零售市場經歷了極大的變化。電商發展迅猛&#xff0c;加上疫情影響&#xff0c;消費者購物習慣出現轉向。而作為美國零售巨頭&#xff0c;沃爾瑪&#xff08;Walmart&#xff09;憑借高達95%的線下覆蓋率&#xff0c;始終是品牌和賣家不可忽視的渠道。…

一文詳解 Linux下的開源打印系統CUPS(Common UNIX Printing System)

文章目錄 前言一、CUPS 簡介二、CUPS 常用指令解析2.1 安裝 CUPS2.2 啟動/重啟服務2.3 添加打印機&#xff08;核心操作&#xff09;2.4 設置默認打印機2.5 打印文件2.6 查看打印任務2.7 取消打印任務2.8 查看、移除已添加的打印機 三、調試與常見問題3.1 日志查看3.2 驅動問題…

React useCallback函數

應用場景&#xff1a;父組件向子組件傳遞函數類型的props時

python 桌面程序開發簡述及示例

Python桌面程序開發簡述及示例 Python憑借其簡潔的語法和豐富的庫支持,非常適合開發跨平臺的桌面應用程序。本文將介紹Python桌面開發的主要方法,并提供實際代碼示例。 一、Python桌面開發主要方法 1.1 Tkinter(標準庫) Python內置的GUI庫,適合開發簡單桌面應用 1.2 …

數字智慧方案5875丨智慧交通樞紐綜合解決方案(43頁PPT)(文末有下載方式)

篇幅所限&#xff0c;本文只能提供部分資料內容&#xff0c;完整資料請看下面鏈接 https://download.csdn.net/download/2301_78256053/89575708 資料解讀&#xff1a;智慧交通樞紐綜合解決方案 詳細資料請看本解讀文章的最后內容。 隨著城市化進程的加速和交通需求的不斷增…

企業級分布式 MCP 方案

飛書原文檔鏈接地址&#xff1a;https://ik3te1knhq.feishu.cn/wiki/D8kSwC9tFi61CMkRdd8cMxNTnpg 企業級分布式 MCP 方案 [!TIP] 背景&#xff1a;現階段 MCP Client 和 MCP Server 是一對一的連接方式&#xff0c;若當前 MCP Server 掛掉了&#xff0c;那么 MCP Client 便不…

【AI提示詞】奧卡姆剃刀思維模型專家

提示說明 一位專注于奧卡姆剃刀思維模型的專業人士&#xff0c;擅長將簡潔性原則應用于復雜問題的分析與解決。 提示詞 # Role: 奧卡姆剃刀思維模型專家## Profile - language: 中文 - description: 一位專注于奧卡姆剃刀思維模型的專業人士&#xff0c;擅長將簡潔性原則應用…

2.1 行列式

引言 行列式是線性代數的核心工具&#xff0c;貫穿矩陣運算、特征值計算與微分方程求解。本文系統梳理2.1節核心考點&#xff0c;結合公式速查與典型例題&#xff0c;助你高效突破行列式難點&#xff01; 考點一&#xff1a;數值型行列式計算 1?? 行列式的定義 (1) 定義方…

單詞規律(簡單)

思路和同構字符串那道題一樣。、但是這道題要注意的地方就是&#xff0c;檢查 pattern 和 s 的單詞數量是否一致以及在進行字符串比較的時候應該用equals來進行比較&#xff0c;而不能用“&#xff01;”&#xff0c;“&#xff01;”比較的是對象引用而非內容。 class Soluti…

【C++】認識map和set

目錄 前言&#xff1a; 一&#xff1a;認識map和set 二&#xff1a;map和set的使用 1.set的使用 2.map的使用 三&#xff1a;map的insert方法返回值 四&#xff1a;map的[ ]的使用 五&#xff1a;multiset和multimap 六&#xff1a;map和set的底層數據結構 七&#x…

Mybatis中的一級二級緩存掃盲

思維導圖&#xff1a; MyBatis 提供了一級緩存和二級緩存機制&#xff0c;用于提高數據庫查詢的性能&#xff0c;減少對數據庫的訪問次數。&#xff08;本質上是減少IO次數&#xff09;。 一級緩存 1. 概念 一級緩存也稱為會話緩存&#xff0c;它是基于 SqlSession 的緩存。在同…

uniapp 實現低功耗藍牙連接并讀寫數據實戰指南

在物聯網應用場景中&#xff0c;低功耗藍牙&#xff08;BLE&#xff09;憑借其低能耗、連接便捷的特點&#xff0c;成為設備間數據交互的重要方式。Uniapp 作為一款跨平臺開發框架&#xff0c;提供了豐富的 API 支持&#xff0c;使得在多個端實現低功耗藍牙功能變得輕松高效。本…

OpenSSL應用實踐:嵌入式數據安全實戰指南

文章目錄 OpenSSL應用實踐:嵌入式數據安全實戰指南一、嵌入式安全現狀與OpenSSL適配方案1.1 嵌入式安全挑戰1.2 OpenSSL精簡方案二、開發環境搭建2.1 交叉編譯工具鏈2.2 OpenSSL交叉編譯三、核心功能實現3.1 AES-GCM加密實踐四、實戰項目:安全OTA升級4.1 系統架構4.2 關鍵代碼…

harmonyOS 手機,雙折疊,平板,PC端屏幕適配

由于HarmonyOS設備的屏幕尺寸和分辨率各不相同&#xff0c;開發者需要采取適當的措施來適配不同的屏幕。 1.EntryAbility.ets文件里&#xff1a;onWindowStageCreate方法里判斷設備類型&#xff0c; 如果是pad&#xff0c;需全屏展示&#xff08;按客戶需求來&#xff0c;本次…

跟韓學AiOps系列之2025學MySQL系列_如何在MySQL中開啟和提交事務?!

跟韓學AiOps系列之2025學MySQL系列_如何在MySQL中開啟和提交事務&#xff1f;! 文章目錄 一、事務的基本操作1. 開啟事務2. 執行事務內操作3. 提交事務4. 回滾事務 二、驗證示例&#xff08;適用于 MySQL 5.7&#xff09;步驟 1&#xff1a;準備測試表和數據步驟 2&#xff1a…

Java生成微信小程序碼及小程序短鏈接

使用wx-java-miniapp-spring-boot-starter 生成微信小程序碼及小程序短鏈接 在pom.xml文件中引入依賴 <dependency><groupId>com.github.binarywang</groupId><artifactId>wx-java-miniapp-spring-boot-starter</artifactId><version>4.7…

如何讓通義千問大模型支持結構化輸出?

之前的文章提到通義千問API無法通過with_structured_output/json schema的方式支持結構化輸出&#xff0c;如果就是想使用通義千問大模型做結構化輸出&#xff0c;應該怎么辦呢&#xff1f;有兩種辦法 使用Ollama來運行通義千問大模型 從Ollama博客文章 Structured output 中…

一條 SQL 查詢語句是如何執行的(MySQL)

第一講&#xff1a;一條 SQL 查詢語句是如何執行的 總覽圖示 MySQL 查詢的執行流程可以大致分為以下步驟&#xff08;如圖所示&#xff09;&#xff1a; 連接器&#xff08;Connection&#xff09;查詢緩存&#xff08;Query Cache&#xff0c;MySQL 8.0 已廢棄&#xff09;…

汽車OTA在線升級法規分析

摘要 本文介紹了R156法規即《關于批準車輛的軟件升級和軟件升級管理體系統一規定的法規》、該法規專注于汽車軟件升級功能&#xff0c;并為此提出了一系列具體要求&#xff0c;旨在確保軟件升級流程的安全性、可控性和合規性&#xff0c;從而順應汽車行業智能化、聯網化的發展趨…

Notepad編輯器實現換行符替換

在不同的Note編輯器中&#xff0c;批量把換行替換為空的方法有所不同&#xff0c;以下是常見編輯器的操作方法&#xff1a; Notepad 打開文件后&#xff0c;按CtrlH打開“查找和替換”對話框&#xff0c;在“查找”字段中輸入\r\n&#xff0c;在“替換為”字段中輸入一個空格…