從文件到文件描述符:理解程序與文件的交互本質

一、理解文件

拋一個概念:

文件 = 內容 + 屬性

1. 那么,空文件有大小嗎?答案是有的。因為空文件指的是文件內容為空,文件屬性也要占據大小啊。

將來對文件操作,無非分為兩類:

1.對文件內容做修改

2.對文件屬性做修改

2. 在學習C語言時,我們訪問一個文件,都要先把對應的文件打開,為什么?

因為要訪問文件的內容和屬性,內容和屬性都是數據,所謂的訪問就是對文件進行增刪查改,都是由CPU執行你的代碼進行訪問,根據馮諾依曼體系結構,要對文件進行操作,你的文件必須在物理內存里,所以,我們把文件打開本質上是把文件加載到內存中

3. 文件有很多啊,被打開的文件在物理內存上,如果一個文件沒有被打開呢?沒有被打開的文件就在磁盤上。

所以,將來學習文件就被分為兩種:被打開的文件和沒有被打開的文件(文件系統)

4. 誰打開的文件?

CPU在調度這個進程時,執行到 fopen 的時候就打開文件。文件是在磁盤上的,磁盤是硬件,誰能去訪問磁盤呢?是操作系統。所以,是用戶通過bash,啟動進程,進程通過操作系統打開文件的

那么,這時候就有人說了,我用的是C語言的庫函數啊,沒調用系統調用怎么訪問的操作系統。是因為庫函數底層對系統調用進行了封裝

5. 一個進程可以打開多個文件嗎?答案是可以的。那如果有多個進程呢?

所以,OS內一定同時存在大量被打開的文件,OS要不要對這些文件進行管理呢肯定是要的。怎么管理?先描述在組織

所以,OS一定存在一種數據結構體,描述被打開的文件,如同PCB一樣

6. 進程有 task_struct ,未來進程也有打開的文件,我們平時研究打開文件,是在研究什么?

本質是研究進程與文件的關系

二、回顧C文件接口

1. 回顧C接口

//pathname (路徑)+文件名
//mode 打開模式
//成功返回文件指針,失敗返回NULL
FILE* fopen(const char* pathname, const char* mode);//打開文件
//關閉文件,成功返回0,失敗返回EOF
int fclose(FILE* stream);
mode:
r:以讀的方式打開文件,定位在文件的開始
r+:以讀寫的方式打開文件,定位在文件的開始
w:以寫的方式打開文件,文件不存在就創建文件,或者清空文件,定位在文件開始
w+:以讀寫的方式打開文件,文件不存在就創建文件,反之清空文件,文件指針定位在文件開始
a:以追加(寫)的方式打開文件,文件不存在就創建文件,定位在文件末尾
a+:以讀寫的方式打開文件,文件不存在就創建文件,寫入時,在文件末尾,讀取時,文件開始。

. 以 w 模式打開文件

在這里插入圖片描述

. 以 a 模式打開文件

在這里插入圖片描述

剩下的就不再枚舉了,大家可以自己驗證一下。

2. 文件讀取是有讀取位置的,那么怎么理解這個呢?

所謂的文件我們可以把它看做成一個“一維數組”,文件的位置不就是數組下標嗎

這時候就有人不理解了,文件是怎么被看做是一個一維數組的

我們可以把文件里的內容看做是一個長字符串,只不過這個長字符串里面有許多換行符而已。這樣應該理解了吧。

接下來看下面的現象,又是怎么回事呢?

在這里插入圖片描述

這是怎么回事呢?當我們向文件里寫入內容時,肯定要先打開文件,當識別到 > 符號時,就會以 w 的方式打開文件,所以就可以進行寫入或者清空文件了

在這里插入圖片描述

相同的道理,>> 符號就會以 a 的方式打開文件,在文件末尾進行追加數據

補充:

向顯示器寫入12345,是寫入了一個int 12345 還是向顯示器寫入了 ‘1’,‘2’,‘3’,‘4’,‘5’?

通過鍵盤輸入12345,輸入了一個 ‘1’,‘2’,‘3’,‘4’,‘5’,還是輸入了 int 12345?

答案是輸入了一個個字符。所以顯示器和鍵盤也叫字符設備

int x = 100 , printf(“%d”,x); ,int 占4個字節,%d表明是一個整數,所以 printf 在向顯示器打印的時候會將數據拆分成一個個字符,輸出到顯示器上,所以 printf 也叫格式化輸出。同理,scanf 函數從字符設備上一個個讀取字符,所以也叫做格式化輸入

那么,顯示器和鍵盤是文件嗎當然是文件。它和我們在軟件層上創建的文件獲取數據和輸出數據是類似的,它就是一個文本文件

那么什么是二進制文件呢?

以二進制形式存儲數據的文件

我們在C語言中學習的stdin,stdout,strerr是文件嗎當然也是了

在這里插入圖片描述

它們都是FILE* 的文件指針。我們常說進程在啟動的時候會默認打開這三個文件流(其實是打開三個文件),這是為什么呢

大部分進程是需要使用CPU資源進行計算的,而計算就需要數據,計算結果有時候也是需要輸出的,也有可能計算錯誤。所以為了方便起見,進程會默認打開對應的文件

看下面的幾個函數。

在這里插入圖片描述

這說明了什么呢?本質向顯示器打印,就是向stdout中寫入,就如同向文件寫入,因為stdout也是FILE*

文件是在磁盤上的,而打開文件就需要將文件加載到內存里,只有操作系統才可以,訪問操作系統就必須經過系統調用。所以接下來,我們就來看看打開文件的系統調用。

3. 系統調用

//pathname 文件路徑+文件名
//flags 打開文件的方式
int open(const char* pathname, int flags);
//mode 文件的權限
int open(const char* pathname, int flags, mode_t mode);

open可以打開文件,如果文件不存在,是否會創建,取決于標志位(flags)

在這里插入圖片描述

flags(標志位)有很多,我們列舉幾個最常用的。

. O_APPEND 追加,文件不存在也會創建

. O_CREAT 創建文件

. O_RDONLY 只讀方式打開文件

. O_WRONLY 只寫方式打開文件

. O_RDWR 讀寫方式打開文件

. O_TRUNC 清空文件

在這里插入圖片描述

可以看到,O_WRONLY是不會創建文件的

在這里插入圖片描述

O_CREAT會創建文件

在這里插入圖片描述

但是,創建的文件權限怎么是亂碼的呢?這是需要你自己手動設置的。

在這里插入圖片描述
在這里插入圖片描述

但是,文件的權限怎么是644呢還記得umask嗎?就是因為它。每個系統的umask可能不一樣。

我們也可以調用系統調用來設置umask,并且不會影響系統的umask。按就近原則執行umask

在這里插入圖片描述

剩下的選項就不再做詳細介紹了。現在,就來聊聊 open 系統調用的返回值吧

在這里插入圖片描述

4. 理解文件描述符

可以看到,open 的返回值文件描述符是一個個整數它是什么呢

一個進程是可以打開多個文件的,OS內一定有大量的文件被打開,這些文件也是要被管理的

在OS內,如何描述被打開的文件呢?struct file這個結構體內一定直接或間接的包含被打開文件的內容和屬性,以雙鏈表的形式進行管理

自此以后,對文件進行管理就轉變為對鏈表的增刪查改

文件是由進程通過OS打開的所以文件與進程之間也是有著密不可分的聯系

一個進程可以打開多個文件,多個進程也可以打開多個文件,那么,被打開的文件是屬于哪個進程呢

在進程PCB里有一個結構體指針struct file_struct* files,它指向一個struct file-struct(文件描述符表)的結構體,這個結構體內有一個成員struct file* fd_array[],這是一個指針數組,指向struct file。打開文件時,OS分配 struct file 結構體,鏈入到struct file的鏈表中,將新申請的struct file結構體的地址填入到fd_array數組中,給用戶返回數組下標

總結文件描述符的本質就是數組下標

也就是說,在OS角度,識別打開的文件,只認int fd 文件描述符

在這里插入圖片描述

那么,數組下標 0,1,2去哪里了呢?我們說進程默認打開了三個文件流:stdin, stdout, stderr,這不剛好三個嗎?沒錯,0,1,2就是分別給它們三個文件流了

那這時候有人就有疑問了,在學習C語言的時候,我們使用文件接口并沒有用到文件描述符 fd 呀,不是說OS只認 fd 嗎?C語言的文件接口返回類型是FILE*呀。

那么,FILE是什么呢?它其實就是C語言標準庫定義的一個結構體

所以,推測,FILE 結構體里面,一定要封裝一個整數,這個整數就是 fd

現在看來,封裝,不僅僅是對于系統調用接口的封裝,連數據類型也做了封裝

在這里插入圖片描述

打開文件時,OS只給我們做了上述的工作嗎?當然不是了。它還要把磁盤上的文件加載到內存里

文件 = 屬性 + 內容OS會給文件屬性開辟一段空間,給文件內容開辟一段空間(文件緩沖區)。而struct file 結構體里會間接的找到它們

在這里插入圖片描述

我們在使用 write(3, "hello world") 系統調用的時候,是在干什么呢

進程會找到文件描述符表,拿著3號文件描述符找到對應的文件,將數據拷貝到文件內核緩沖區里

所以:write的本質根本就不是寫入到文件里,write的本質是拷貝函數,把數據從用戶空間拷貝到對應文件的內核緩沖區中

那如果讀取文件呢只能從文件緩沖區里面讀取

修改文件呢從內核緩沖區里拷貝數據到用戶緩沖區,進行修改,再拷貝到內核緩沖區,刷新到磁盤上

所以,我們對任何文件內容進行增刪查改,都必須把文件的內容提前預加載到該文件的內核緩沖區中

5. 文件描述符的分配規則

在這里插入圖片描述

我們關閉了文件描述符 0 所指向的文件,所以OS給我們分配了 0,那么如果關閉2號文件呢?我們來驗證一下。

在這里插入圖片描述

由此可見,文件描述符的分配規則是給新打開的文件分配 fd ,從文件描述符表數組中尋找,最小的,沒有被使用的數組下標,作為該文件的 fd

那么,有沒有人疑惑呢?為什么跳過了 1 號描述符呢

我們來試一下。

在這里插入圖片描述

沒有打印,這是為什么呢?因為1號文件描述符指向的文件是 stdout ,我們把它關閉了,然后創建了 log.txt 文件,1號文件描述符就被分配給了 log.txt文件,所以就無法向顯示器打印了那么,既然 1 號文件描述符分配給了 log.txt,數據是不是在 log.txt 里呢

在這里插入圖片描述

可以看到,是沒有的,那是怎么回事呢?我們對代碼做出些許改動,看看結果。

在這里插入圖片描述

現在,我們換一種方式檢驗結果。

在這里插入圖片描述

我們用了兩種方式,表達的都是同一個問題。這是為什么呢?

這與緩沖區有關。我們需要等到后面才能解釋。

一般,我們不采用這種關閉某個文件,打開另一個文件的做法。所以,接下來,我們需要先了解一個系統調用。

//用于復制文件描述符
//oldfd文件描述符復制到newfd文件描述符上
//如果newfd已經打開,dup2會先關閉它,然后再進行復制。
//成功,返回新的文件描述符newfd
//失敗,返回-1,并設置errno
int dup2(int oldfd, int newfd);

利用這個系統調用,我們可以寫一個輸出重定向的代碼。

在這里插入圖片描述

有眼尖的伙伴就發現了,它打印的順序怎么和我們代碼所寫的順序不一樣呢?這也是因為緩沖區的緣故。

6. 如果我們創建子進程,子進程是如何看待父進程打開的文件的?

父進程創建子進程,OS會給子進程分配PCB,虛擬地址空間,頁表...,當然了,也包括今天的文件描述符表(struct file_struct)

PCB,,虛擬地址空間,頁表是以父進程為模板的,文件描述符表當然也沒有例外,也就是說,父進程打開的文件,子進程也會打開,那么,OS會重新再加載一份文件嗎當然不會了。我們是創建了一個新的進程,又不是打開了新的文件。

那么,子進程以父進程為模板,文件描述符表中的struct file* fd_array[]就是淺拷貝,也就意味著子進程和父進程指向的是同一個文件

不知道大家看到這里有沒有問題呢?

既然子進程和父進程指向的是同一個文件,那如果子進程關閉了某些文件,是不是父進程也無法使用呢

當然不會了進程是具有獨立性的。父子進程指向同一個文件,為了保證進程的獨立性,OS采用了引用計數的方法,子進程關閉了某個文件,OS就對計數做 - - 操作。直到計數為0,就會關閉該文件

我們常說進程默認會打開 stdin, stdout, stderr 這三個文件流,我們在命令行上啟動的進程,我們是沒有打開這幾個文件流的。因此,進程都是通過父進程繼承來的

我們可以驗證一下。

在這里插入圖片描述

7. 如果程序替換,不會創建新進程,會影響我們歷史打開的文件嗎?

答案是不會

程序替換,加載的是新的代碼和數據,跟文件有什么關系。所以,程序替換不會影響文件

在這里插入圖片描述

今天的文章分享到此結束,覺得不錯的給個一鍵三連吧。

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

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

相關文章

優化算法專欄——閱讀導引

前言 提醒: 文章內容為方便作者自己后日復習與查閱而進行的書寫與發布,其中引用內容都會使用鏈接表明出處(如有侵權問題,請及時聯系)。 其中內容多為一次書寫,缺少檢查與訂正,如有問題或其他拓展…

[ The Missing Semester of Your CS Education ] 學習筆記 Vim篇

“Writing English words and writing code are very different activities. When programming, you spend more time switching files, reading, navigating, and editing code compared to writing a long stream.” —— < The Missing Semester of Your CS Education &g…

Linux 系統中定時執行指定命令 crontab 定時任務配置

crontab 定時任務配置是 Linux/Unix 系統中用于自動、周期性執行指定命令或腳本的工具&#xff0c;相當于系統的 “定時鬧鐘”。它可以讓系統在預設的時間&#xff08;如每天凌晨、每周一、每月 1 號等&#xff09;自動完成重復性工作&#xff0c;無需人工干預。自動化運維定期…

[ Leetcode ]---快樂數

題目鏈接 Leetcode快樂數 題目描述 如下圖&#xff1a; 題目解析&#xff1a; 1.雙指針法 算法核心思路 判斷快樂數的關鍵挑戰是如何檢測是否進入無限循環。這里使用了快慢指針法&#xff08;Floyd 循環檢測算法&#xff09;&#xff0c;這是一種高效檢測循環的技巧&#…

智慧社區構建——2

1.實現Token校驗## Token校驗URLjson GET /checkToken 參數json HttpServletRequest request 返回json {"msg": "操作成功","code": 200,"status": "ok" }{"msg": "操作成功","code": 200,&q…

K-Means聚類:當數據沒有標簽時,如何讓計算機自動“物以類聚”?

K-Means聚類&#xff1a;當數據沒有標簽時&#xff0c;如何讓計算機自動“物以類聚”&#xff1f;&#x1f44b; 大家好&#xff0c;我是小瑞瑞&#xff01;歡迎回到我的專欄&#xff01; 在我們之前的旅程中&#xff0c;解決的問題大多都有一個明確的“目標”&#xff0c;比如…

萬事皆可用 GeeLark AI

在今年4月&#xff0c;GeeLark AI 全面接入 DeepSeek AI 大模型&#xff0c;你可以在獨立窗口中便捷地使用 GeeLark AI。除了幫助你編寫文案等基礎內容&#xff0c;在使用 GeeLark 過程中&#xff0c;如果遇到問題&#xff0c;也可以通過詢問 GeeLark AI&#xff0c;及時獲取幫…

3D 高保真處理:聲網讓游戲聲音隨角色動作變化

傳統游戲的聲音體驗像老式收音機&#xff0c;不管聲源位置、距離和障礙物&#xff0c;僅靠左右聲道機械調音量&#xff0c;毫無方向感和空間感&#xff0c;如同蒙眼聽聲辨位。射擊游戲中敵人從左邊來&#xff0c;耳機卻兩邊同響且音量相近&#xff0c;讓人暈頭轉向&#xff1b;…

Nestjs框架: 請求生命周期與應用生命周期

概述 在 NestJS 框架中&#xff0c;中間件&#xff08;Middleware&#xff09;、管道&#xff08;Pipes&#xff09;、過濾器&#xff08;Filters&#xff09;、攔截器&#xff08;Interceptors&#xff09; 均屬于請求處理流程的核心組件&#xff0c;它們共同構成了 NestJS 的…

Nastool+cpolar:群暉NAS用戶的全場景影音自由方案

文章目錄前言1. 本地搭建Nastool2. nastool基礎設置3. 群暉NAS安裝內網穿透工具4. 配置公網地址小結5. 配置固定公網地址**第二版&#xff1a;技術整合與效率提升導向****第二版&#xff1a;技術整合與效率提升導向****第二版&#xff1a;技術整合與效率提升導向**Nastool與cpo…

從零開始:Kaggle 競賽實戰入門指南

一、Kaggle社區概述 Kaggle 是全球最大的數據科學和機器學習社區&#xff0c;由Anthony Goldbloom于2010年創立&#xff0c;2017年被Google收購。平臺專注于數據科學競賽、開源數據集共享、協作編程以及技能學習&#xff0c;吸引了從初學者到專業數據科學家的廣泛用戶群體。 …

sqli-labs:Less-16關卡詳細解析

1. 思路&#x1f680; 本關的SQL語句為&#xff1a; $uname".$uname."; $passwd".$passwd."; $sql"SELECT username, password FROM users WHERE username($uname) and password($passwd) LIMIT 0,1";注入類型&#xff1a;字符串型&#xff08;…

Lipschitz連續函數

Lipschitz function 一、說明 在數學分析中&#xff0c;Lipschitz連續性以德國 數學家 魯道夫利普希茨 (Rudolf Lipschitz)的名字命名&#xff0c;是函數一致連續性的強形式。直觀地說&#xff0c;Lipschitz連續函數的變化速度有限&#xff1a;存在一個實數&#xff0c;使得對于…

Dynamics 365 business central 與Shopify集成

Dynamics 365 Business Central&#xff08;簡稱 D365 BC&#xff09; 與 Shopify 的集成&#xff0c;能幫助企業實現前端電商平臺&#xff08;Shopify&#xff09;與后端 ERP 系統&#xff08;Business Central&#xff09;之間的無縫數據同步&#xff0c;是一種典型的 ERP 與…

TCP RTO 與丟包檢測

TCP RTO 是它 40 多年前唯一丟包檢測策略&#xff0c;也是當前最后的丟包檢測兜底策略&#xff0c;它幾乎從沒變過。 有個咨詢挺有趣&#xff0c;以其案例為背景寫篇隨筆。大致意思是&#xff0c;嫌 TCP RTO 太大&#xff0c;游戲場景丟包卡頓怎么辦&#xff1f;我提供了幾行代…

安裝php和配置環境變量

為了簡單方便&#xff0c;先下載vscode然后下載對應的php安裝包&#xff0c;然后配置環境變量&#xff0c;然后點擊運行即可下載對應版本的php&#xff0c;這個版本湊合用然后下載完之后解壓配置環境變量搜索環境變量將路徑添加到環境變量中然后打開vscode添加變量具體看實際路…

Rabbit MQ的消息模式-Java原生代碼

一.簡單模式1.1.核心邏輯生產者 → 隊列 → 單個消費者&#xff08;1:1 直連&#xff09;&#xff0c;消息被消費后自動從隊列刪除。1.2.關鍵特性無交換器&#xff08;其實使用的是默認交換機不是顯示指定&#xff09;&#xff0c;直接指定隊列 消息默認自動確認&#xff08;au…

【lucene】使用docvalues的案例

下面給出一段 可直接跑通 的 Lucene 8.5.0 示例代碼&#xff0c;演示如何1. 建索引時為兩個字段啟用 DocValues&#xff08;一個 NumericDocValues&#xff0c;一個 SortedDocValues&#xff09;&#xff1b; 2. 用 IndexSearcher 按 DocValues 排序&#xff1b; 3. 用 Facet…

IntelliJ IDEA 配置 Maven 阿里云鏡像加速源全流程

1. 為什么要加國內鏡像源&#xff1f;國內網絡訪問 Maven 中央倉庫經常超時、依賴下載極慢或失敗。配置阿里云等國內鏡像后&#xff0c;Java 項目依賴下載飛快&#xff0c;極大提升開發效率&#xff0c;是中國開發者必做優化&#xff01;2. 添加阿里云鏡像源的步驟&#xff08;…

【worklist】worklist的hl7、dicom是什么關系

HL7和DICOM在Worklist系統中是互補的關系&#xff0c;它們各自承擔不同的角色&#xff0c;但協同工作以實現完整的醫療信息系統集成。HL7與DICOM Worklist的關系1. 功能分工DICOM Worklist (Modality Worklist - MWL)主要用于影像設備獲取患者和檢查信息基于DICOM協議&#xff…