Linux:文件描述符fd、系統調用open

目錄

一、文件基礎認識

二、C語言操作文件的接口

1.> 和 >>

2.理解“當前路徑”

三、相關系統調用

1.open

2.文件描述符

3.一切皆文件

4.再次理解重定向


一、文件基礎認識

  • 文件 = 內容 + 屬性。換句話說,如果在電腦上新建了一個空白文檔,它雖然沒有內容,但也是占據磁盤空間的。
  • 想要修改一個文件的內容,比如用WPS這樣的軟件操作文件內容,本質上都需要CPU完成相關的指令,而CPU又只與內存交互,所以,打開文件的含義其實就是把文件加載到內存中
  • 在我們眼里,我們雙擊了一個文件就是打開了文件,但是在操作系統看來,并不是我們打開了文件,而是某一個正在運行的進程,文件是由進程打開的
  • 一個進程可以打開多個文件。
  • 操作系統管理多個被打開文件,必然也會像操作系統管理多個進程一樣,利用面向對象和數據結構,因此,內核中必然定義了結構體來描述被打開的文件。
  • 從操作系統管理文件的角度看,文件被區分為被打開的文件(在內存中)和沒有打開的文件(在磁盤中)。

二、C語言操作文件的接口

? ? ? ? fopen"w"方法打開一個文件。

#include <stdio.h>
#include <stdlib.h>
int main()
{FILE* pf = fopen("aaa.txt","w");if(pf == NULL){perror("fopen:");return 1;}const char* str = "aaaaaaaaaaaaaaaaaaaaaa\n";fputs(str,pf);fclose(pf);return 0;
}
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ ./a.out 
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ cat aaa.txt 
aaaaaaaaaaaaaaaaaaaaaa
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ 

? ? ? ? 結果顯示,文件aaa.txt中已經寫入了一段字符串。修改源代碼,將寫入字符串的代碼刪除后,再執行編譯運行一次。

#include <stdio.h>
#include <stdlib.h>
int main()
{FILE* pf = fopen("aaa.txt","w");if(pf == NULL){perror("fopen:");return 1;}
//	const char* str = "aaaaaaaaaaaaaaaaaaaaaa\n";
//	fputs(str,pf);fclose(pf);return 0;
}
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ cat aaa.txt 
aaaaaaaaaaaaaaaaaaaaaa
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ gcc file.c 
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ ./a.out 
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ cat aaa.txt 
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ 

? ? ? ? 結果表明,aaa.txt文件中的內容都消失了。原因在于fopen打開文件的方式"w",使用man手冊查看fopen打開文件方式的說明。

? ? ? ? "w"方式打開文件時,會先清空文件中的所有內容。如果想保留文件中原來的內容做寫入操作,就應該使用"a"的方式打開文件。


1.> 和 >>

utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ cat aaa.txt
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ echo aaaaaaaaaaaa > aaa.txt 
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ cat aaa.txt
aaaaaaaaaaaa
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ echo bbbbbbbbb > aaa.txt 
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ cat aaa.txt
bbbbbbbbb
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ 

? ? ? ? 通過echo做重定向操作向aaa.txt文件中先后寫入兩次,最終效果并不是有兩段字符串,說明重定向操作符">"打開文件的方式本質上也是"w"的方式。(需要一提的是,echo重定向到文件中,本質上也要修改文件的內容,所以一定會打開文件)。


utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ echo aaaaaaaaaaaaaaaa >> aaa.txt 
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ cat aaa.txt 
aaaaaaaaaaaaaaaa
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ echo bbbbbbbbbbbbbbbb >> aaa.txt 
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ cat aaa.txt 
aaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbb
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ 

? ? ? ? 而追加重定向操作符" >> "先后向aaa.txt文件寫入兩次后,最終效果是兩段字符串都被保留了下來,說明 " >> "其實和"a"方式類似,是一種追加的形式。


2.理解“當前路徑”

? ? ? ? 在使用C接口操作文件的時候,經常會聽到說,“如果沒有這個文件,則在當前路徑下新建這個文件”,如何理解這個“當前路徑”

? ? ? ? 最簡單直接的理解,就是我們當前程序的路徑

//file.c
#include <stdio.h>
#include <stdlib.h>
int main()
{FILE* pf = fopen("aaa.txt","w");if(pf == NULL){perror("fopen:");return 1;}fclose(pf);return 0;
}

? ? ? ? 當前路徑就是file.c文件所在路徑,編譯運行前,該路徑下沒有aaa.txt文件,編譯運行后,該路徑下存在名為aaa.txt的文件。

utocoo@utocoo-virtual-machine:~/Desktop/linux/241122$ ll
總計 16
drwxrwxr-x  2 utocoo utocoo 4096 11月 22 12:22 ./
drwxrwxr-x 16 utocoo utocoo 4096 11月 22 12:19 ../
-rw-rw-r--  1 utocoo utocoo  233 11月 22 12:19 file.c
-rw-rw-r--  1 utocoo utocoo   64 11月 22 12:21 Makefile
utocoo@utocoo-virtual-machine:~/Desktop/linux/241122$ make
gcc -o file file.c
utocoo@utocoo-virtual-machine:~/Desktop/linux/241122$ ./file 
utocoo@utocoo-virtual-machine:~/Desktop/linux/241122$ ll
總計 32
drwxrwxr-x  2 utocoo utocoo  4096 11月 22 12:23 ./
drwxrwxr-x 16 utocoo utocoo  4096 11月 22 12:19 ../
-rw-rw-r--  1 utocoo utocoo     0 11月 22 12:23 aaa.txt
-rwxrwxr-x  1 utocoo utocoo 16048 11月 22 12:23 file*
-rw-rw-r--  1 utocoo utocoo   233 11月 22 12:19 file.c
-rw-rw-r--  1 utocoo utocoo    64 11月 22 12:21 Makefile

? ? ? ? ?在文件基礎認識部分,已經提到過,文件是由進程打開的,那么新建一個文件也是由進程完成,進程是如何知道在哪條路徑下新建一個文件呢。

? ? ? ? 在源代碼中打印出進程的PID,運行后,再在/proc路徑下找到對應進程的所在目錄。

while(1)
{printf("PID:%d\n",getpid());sleep(2);
}
PID:2930
PID:2930
PID:2930
PID:2930
PID:2930
PID:2930
PID:2930

? ? ? ? 當前路徑在進程的屬性中其實已經保存好了,是cwd這條信息。因此新建一個文件要被存放到哪里也是確定的。但是進程的工作路徑是可以修改的,雖然進程的前身是一個可執行程序,可執行程序的路徑是確定,但是當可執行程序被操作系統管理起來后變成進程,進程的工作路徑是可以通過chdir指令修改的,那么修改路徑后,再新建一個文件,這個文件的所在路徑不再是修改前的路徑了,而是修改后的路徑。

? ? ? ? 這就表明,所謂的當前路徑,其實是進程在運行的時候的工作路徑,這個路徑是由進程自己記錄的,就是那條cwd信息。

三、相關系統調用

? ? ? ? 系統默認打開三個流,stdin,stdout,stderr,這三個流對應的外設分別為鍵盤、顯示器顯示器。而Linux管理外設,是以文件的方式,即必然存在系統調用system call。因此,C語言的fopen、fclose、fwrite等函數本質是調用了system call

? ? ? ? 下面就來認識Linux下文件相關的system call。

1.open

?

  • pathname就是路徑,傳參方法和C語言的fopen的參數差不多。
  • flags類型為int,傳參的可選項如下所示

? ? ? ? 這些值都是C語言定義的宏,目的是為了實現,只定義一個函數,卻可以同時“傳兩個參數”。比如

#include <stdio.h>
#define ONE 1
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
#define FIVE (1<<4)void Print(int flags)
{if(flags & ONE)printf("1\n");if(flags & TWO)printf("2\n");if(flags & THREE)printf("3\n");if(flags & FOUR)printf("4\n");if(flags & FIVE)printf("5\n");
}
int main()
{Print(ONE);printf("-----------------\n");Print(TWO);printf("-----------------\n");Print(ONE|TWO);printf("-----------------\n");Print(ONE|FOUR|FIVE);return 0;
}


? ? ? ? 如果使用兩個形參的open接口,一般是操作已經存在了的文件,比如bbb.txt文件必須存在,否則會報錯。

int main()
{int fd = open("bbb.txt",O_WRONLY);if(fd == -1){perror("open\n");return 1;}close(fd);return 0;
}
由于bbb.txt不存在,則fd=-1

? ? ? ? ?用open接口實現fopen的"w"方式,文件如果不存在,則新建。而新建一個文件會有權限的初始化,一般普通用戶新建一個文件的權限是0666(-rw-rw-rw-),而普通用戶的權限掩碼umask為0002,實際權限等于初始化權限減去權限掩碼,即(-rw-rw-r--)

? ? ? ? mode即初始化權限碼,一般傳0666,只有flags帶O_CREAT時,mode傳參才有效。

? ? ? ? 一般新建一個文件,在open的第二個參數上,應該傳新建、可寫、寫入時清零,等同于fopen的"w"方式。

int main()
{int fd = open("bbb.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);if(fd == -1){perror("open\n");return 1;}const char* msg = "this is open to w\n";write(fd,msg,strlen(msg));close(fd);return 0;
}

? ? ? ? 原來不存在的文件bbb.txt被創建了出來,并且o的權限少了w,符合預期。


2.文件描述符

????????再來理解open的返回值——文件描述符(int fd)——Linux用整型值描述被打開的文件。

? ? ? ? 這些整型值其實是數組下標,我們知道系統默認打開三個流,其實是三個文件,stdin、stdout、stderr,它們的下標對應為0、1、2,如果先后有序的打開1.txt、2.txt、3.txt,則它們的下標也是有序的為3、4、5。

? ? ? ? 這段話似乎讓你很懵,不過我馬上就要闡述具體的內容。

? ? ? ? 在此之前,要明確,操作文件只能由操作系統來做,因此有C語言的fopen封裝open接口,有C語言定義的FILE指針的流封裝文件描述符fd。

? ? ? ? 實際上,FILE類型是結構體類型,也是封裝了文件描述符int fd。


? ? ? ? ?對int fd的理解。

? ? ? ? 文件描述符的本質,就是數組下標。

  • OS管理進程,這一板塊叫做進程管理,有PCB,Linux下被定義為task_struct。
  • OS管理文件,這一板塊叫做文件管理,在之前介紹了,文件區分為內存中的文件和磁盤中的文件,被加載到內存中的文件,OS要對它們做管理,就必然做“面向對象”和“數據結構”的工作,“面向對象”就是定義結構體,“數據結構”就是把對象存儲到鏈表或者其他數據結構里面。Linux下把這個結構體類型定義為file,結構體內容大致有屬性、方法集、緩沖區、mode(權限碼)、flag、pos以及指向下一個結點的next等。
  • 進程管理和文件管理是兩個獨立的板塊,但是又有關聯。進程可以打開多個文件,那么一個進程打開了哪些文件,該進程必然要做記錄。于是Linux下,task_struct結構體中有一個結構體指針,指向的結構體類型為files_struct,而這個結構體中,有一個數組,數組的每個元素類型為結構體指針,指針指向的結構體類型為file,這個數組被稱為文件描述符表

? ? ? ? 一個進程打開文件后,進程在這個數組中保存指向這個文件的指針,默認這個數組的前三個位置已經被stdin、stdout、stderr這三個文件占用了。?

? ? ? ? 而數組下標,就是文件描述符,為什么close、write等這些接口都用int類型的文件描述符來操作文件,原因很簡單,數組下標式訪問,僅僅是O(1)復雜度

3.一切皆文件

? ? ? ? 硬件一層,由于各種原因,設備的操作方法各不相同,因此每臺計算機都需要裝載相應的驅動。而對于每臺設備的操作函數,它們的函數類型相同,函數內容各不相同。

? ? ? ? file結構體定義了方法集,本質就是函數指針

  • 每一臺設備被視為一個結構體,方法集指向了該設備的操作方法。
  • 當系統調用read讀取某個外設的內容,實際上就是函數回調的形式,用函數指針調用外設的讀函數。

4.再次理解重定向

? ? ? ? 文件描述符的分配規則:一定會把最小的數組下標利用起來,如果存在沒有被利用的較小下標,則會分配給最新打開的文件,比如打開b文件前,將已經打開的a文件關閉,則打開b文件后,a文件的較小fd會分配給b文件。

? ? ? ? 上面這段話,其實就是重定向的實現原理。


? ? ? ? 輸出重定向:本該輸出到屏幕的語句卻輸出到了bbb.txt。?

int main()
{close(1);int fd = open("bbb.txt",O_WRONLY);printf("這段話本該輸出到屏幕\n");return 0;
}

? ? ? ? 原因就是在執行完close(1)語句后,當前進程的文件描述符表中數組下標為1的位置不再是指向屏幕文件的指針,而又打開了bbb.txt文件,則1號下標的指針指向了bbb.txt文件printf底層封裝的write傳參的fd值還是1,因此,這句字符串被寫進了1位置指向的bbb.txt文件的緩沖區。

? ? ? ? 所以,重定向的本質,就是文件指針在文件描述符表中的位置發生了變化,文件描述符表是一個數組,即數組下標發生了改變,比如原來4號下標指向A.txt文件,通過重定向讓1號下標指向了A.txt,這樣一來,上層向顯示器打印的內容其實都被輸出到了A.txt。


?????????有一個專門用來拷貝文件描述符的系統調用——dup

? ? ? ? ?想把打印到屏幕的內容重定向到bbb.txt,可以用dup2來實現。

? ? ? ? 大致意思是用oldfd的值覆蓋到newfd

int main()
{int fd = open("bbb.txt",O_WRONLY);dup2(fd,1);printf("----\n");return 0;
}

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

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

相關文章

鴻蒙動態路由實現方案

背景 隨著CSDN 鴻蒙APP 業務功能的增加&#xff0c;以及為了與iOS、Android 端統一頁面跳轉路由&#xff0c;以及動態下發路由鏈接&#xff0c;路由重定向等功能。鴻蒙動態路由方案的實現迫在眉睫。 實現方案 鴻蒙版本動態路由的實現原理&#xff0c;類似于 iOS與Android的實…

計算機網絡 (42)遠程終端協議TELNET

前言 Telnet&#xff08;Telecommunication Network Protocol&#xff09;是一種網絡協議&#xff0c;屬于TCP/IP協議族&#xff0c;主要用于提供遠程登錄服務。 一、概述 Telnet協議是一種遠程終端協議&#xff0c;它允許用戶通過終端仿真器連接到遠程主機&#xff0c;并在遠程…

汽車網絡信息安全-ISO/SAE 21434解析(上)

目錄 概述 第四章-概述 1. 研究對象和范圍 2. 風險管理 第五章-組織級網絡安全管理 1. 網絡安全治理&#xff08;cybersecurity governance&#xff09; 2. 網絡安全文化&#xff08;cybersecurity culture) 3. 信息共享&#xff08;Information Sharing) 4. 管理體系…

【0393】Postgres內核 checkpointer process ③ 構建 WAL records 工作緩存區

1. 初始化 ThisTimeLineID、RedoRecPtr 函數 InitXLOGAccess() 內部會初始化 ThisTimeLineID、wal_segment_size、doPageWrites 和 RedoRecPtr 等全局變量。 下面是這四個變量初始化前的值: (gdb) p ThisTimeLineID $125 = 0 (gdb) p wal_segment_size $126 = 16777216 (gdb…

cursor+deepseek構建自己的AI編程助手

文章目錄 準備工作在Cursor中添加deepseek 準備工作 下載安裝Cursor &#xff08;默認安裝在C盤&#xff09; 注冊deepseek獲取API key 在Cursor中添加deepseek 1、打開cursor&#xff0c;選擇設置 選擇Model&#xff0c;添加deepseek-chat 注意這里去掉其他的勾選項&…

微調神經機器翻譯模型全流程

MBART: Multilingual Denoising Pre-training for Neural Machine Translation 模型下載 mBART 是一個基于序列到序列的去噪自編碼器&#xff0c;使用 BART 目標在多種語言的大規模單語語料庫上進行預訓練。mBART 是首批通過去噪完整文本在多種語言上預訓練序列到序列模型的方…

潯川社團官方文章被 Devpress 社區收錄!

潯川社團官方文章被 Devpress 社區收錄&#xff01; 親愛的潯川社團成員們以及關注我們的朋友們&#xff1a; 在這個充滿活力與機遇的社團發展歷程中&#xff0c;我們迎來了一則令人振奮的喜訊&#xff01;潯川社團精心創作的官方文章&#xff0c;成功被 Devpress 社區收錄啦&a…

STM32網絡通訊之CubeMX實現LWIP項目設計(十五)

STM32F407 系列文章 - ETH-LWIP-CubeMX&#xff08;十五&#xff09; 目錄 前言 一、軟件設計 二、CubeMX實現 1.配置前準備 2.CubeMX配置 1.ETH模塊配置 2.時鐘模塊配置 3.中斷模塊配置 4.RCC及SYS配置 5.LWIP模塊配置 3.生成代碼 1.main文件 2.用戶層源文件 3.…

簡單組合邏輯

多路選擇器 在多路數據傳輸過程中&#xff0c;能夠將任意一路選出來的電路叫做數據選擇器&#xff0c;也稱多路選擇器。對于一個具有2^n個輸入和一個輸出的多路選擇器&#xff0c;有n個選擇變量&#xff0c;多路選擇器也是FPGA內部的一個基本資源&#xff0c;主要用于內部信號的…

【Unity-Game4Automation PRO 插件】

Game4Automation PRO 插件 是一個用于 Unity 引擎 的工業自動化仿真工具&#xff0c;它提供了對工業自動化領域的仿真和虛擬調試支持&#xff0c;特別是在與工業機器人、生產線、PLC 系統的集成方面。該插件旨在將工業自動化的實時仿真與游戲開發的高質量 3D 可視化能力結合起來…

【安卓開發】【Android】總結:安卓技能樹

【持續更新】 對筆者在安卓開發的實踐中認為必要的知識點和遇到的問題進行總結。 一、基礎知識部分 1、Android Studio軟件使用 軟件界面 最新的版本是瓢蟲&#xff08;Ladybug&#xff09;&#xff0c;bug的確挺多。筆者更習慣使用電鰻&#xff08;Electric Eel&#xff0…

鴻蒙打包發布

HarmonyOS應用/元服務發布&#xff08;打包發布&#xff09; https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V13/ide-publish-app-V13?catalogVersionV13 密鑰&#xff1a;包含非對稱加密中使用的公鑰和私鑰&#xff0c;存儲在密鑰庫文件中&#xff0c;格式…

Spring Boot 下的Swagger 3.0 與 Swagger 2.0 的詳細對比

先說結論&#xff1a; Swgger 3.0 與Swagger 2.0 區別很大&#xff0c;Swagger3.0用了最新的注釋實現更強大的功能&#xff0c;同時使得代碼更優雅。 就個人而言&#xff0c;如果新項目推薦使用Swgger 3.0&#xff0c;對于工具而言新的一定比舊的好&#xff1b;對接于舊項目原…

神經網絡基礎-價格分類案例

文章目錄 1. 需求分析2. 導入所需工具包3. 構建數據集4. 構建分類網絡模型5. 訓練模型6. 模型訓練7. 評估模型8. 模型優化 學習目標&#xff1a; 掌握構建分類模型流程動手實踐整個過程 1. 需求分析 小明創辦了一家手機公司&#xff0c;他不知道如何估算手機產品的價格。為了…

SAP 固定資產常用的數據表有哪些,他們是怎么記錄數據的?

在SAP系統中&#xff0c;固定資產管理&#xff08;FI-AA&#xff09;涉及多個核心數據表&#xff0c;用于記錄資產主數據、折舊、交易等。以下是常用的數據表及其記錄數據的邏輯&#xff1a; 1. ANKT - 資產主數據表 功能&#xff1a;存儲資產主數據的文本描述。 字段&#x…

光伏儲能電解水制氫仿真模型Matlab/Simulink

今天更新的內容為光伏儲能制氫技術&#xff0c;這個方向我之前在21年就系統研究并發表過相關文章&#xff0c;經過這幾年的發展&#xff0c;綠色制氫技術也受到更多高校的注意&#xff0c;本篇博客也是在原先文章的基礎上進行更新。 首先讓大家熟悉一下綠氫制取技術這個概念&a…

Redis 3.2.1在Win10系統上的安裝教程

諸神緘默不語-個人CSDN博文目錄 這個文件可以跟我要&#xff0c;也可以從官網下載&#xff1a;https://github.com/MicrosoftArchive/redis/releases 這個是微軟以前維護的Windows版Redis安裝包&#xff0c;如果想要比較新的版本可以從別人維護的項目里下&#xff08;https://…

基于springboot+vue.js+uniapp技術開發的一套大型企業MES生產管理系統源碼,支持多端管理

企業級智能制造MES系統源碼&#xff0c;技術架構&#xff1a;springboot vue-element-plus-admin 企業級云MES全套源碼&#xff0c;支持app、小程序、H5、臺后管理端 MES指的是制造企業生產過程執行系統&#xff0c;是一套面向制造企業車間執行層的生產信息化管理系統。MES系…

【Redis】Redis事務和Lua腳本的區別

Redis事務 概念 事務&#xff1a;Redis事務是一組命令的集合&#xff0c;這些命令會被序列化地執行&#xff0c;中間不會被其他命令插入。 MULTI/EXEC&#xff1a;Redis事務通過MULTI命令開始&#xff0c;通過EXEC命令執行所有已入隊的命令。 特點 原子性&#xff1a; 事務…

frameworks 之 AMS與ActivityThread交互

frameworks 之 AMS與ActivityThread交互 1. 類關系2. 流程2.1 AMS流程2.1 ActivityThread流程 3. 堆棧 講解AMS 如何和 ActivityThread 生命周期調用流程 涉及到的類如下 frameworks/base/core/java/android/app/servertransaction/ResumeActivityItem.javaframeworks/base/cor…