文件是Linux系統中最基本和最常用的資料儲存方式,它們可以是文字文件,二進位文件,設備文件,目錄文件等。檔案的讀寫是Linux程式設計中最重要的操作之一,它涉及到檔案描述符,緩衝區,系統調用,庫函數等概念。在本文中,我們將介紹Linux檔案I/O的基本原理和方法,包括打開,關閉,讀取,寫入,定位,截斷,同步等操作,並舉例說明它們的使用方法和注意事項。
#檔案描述子(File Descriptor)
a small, nonnegative integer for use in subsequent system calls (read(2), write(2), lseek(2), fcntl(2), etc.) ($man 2 open). 當一個程式開始執行時一般會有3個已經開啟的檔案描述子:
- 0 :STDIN_FIFLENO,標準輸入stdin
- # 1 :STDOUT_FILENO,標準輸出stdout
- # 2 :STDERR_FILENO,標準錯誤stderror
#fd原理
- fd從0開始, 找出最小的未被使用的描述符, 把文件表指標與文件表描述符建立對應關係(VS pid是一直向上漲,滿了再回來找)
- # 文件描述符就是一個int, 用來代表一個打開的文件, 但是文件的管理資訊不能夠不是存放在文件描述符中,當使用open()函數打開一個文件時, OS會將文件的相關信息加載到文件表等資料結構中, 但出於安全和效率等因素的考慮, 文件表等資料結構並不適合直接操作, 而是給該結構指定一個編號, 使用編號來進行操作, 該編號就是文件描述符
- OS會為每個進程內部維護一張檔案描述符總表, 當有新的檔案描述子需求時, 會去總表中查找最小的未被使用的描述符返回, 文件描述符雖然是int類型,但其實是非負整數, 也就是0~OPEN_MAX(當前系統中為1024), 其中0,1,2已被系統佔用,分別表示stdin, stdout,stderror
- # 使用close()關閉fd時, 就是將fd和文件表結構之間的對應關係從總表中移除, 但不一定會刪除文件表結構, 只有當文件表沒有與其他任何fd對應時(也就是一個文件表可以同時對應多個fd)才會刪除文件表, close()也不會改變文件描述符本身的整數值, 只會讓該文件描述符無法代表一個文件而已
- duplicate fdVS copy fd:dup是把old_fd對應的檔案表指標複製到new_fd, 而不是int new_fd=old_fd
-
UNIX使用三種資料結構描述開啟的檔案:每個行程中用來描述目前行程開啟檔案的檔案描述子表,表示目前檔案狀態的檔案狀態識別表,和用於找到檔案i節點(索引節點)的V節點表,Linux中並不使用這種Vnode結構,取而代之的是一種通用的inode結構,但本質沒有區別,inode是在讀取檔案時透過檔案系統從磁碟匯入的檔案位置

檔案描述標誌(File Descriptor Flag)
#當下的系統只有一個檔案描述符標誌close-on-exec
,只是一個標誌,當進程fork一個子進程的時候,在子進程中呼叫了exec函數時就用到了該標誌。意義是執行exec前是否要關閉這個檔案描述符。
- 一般我們會呼叫exec執行另一個程序,此時會用全新的程序取代子進程的正文,數據,堆和棧等。此時保存檔案描述符的變數當然也不存在了,我們就無法關閉無用的檔案描述符了。所以通常我們會fork子程序後在子程序中直接執行close關掉無用的檔案描述符,然後再執行exec。但在複雜系統中,有時我們fork子程序時已經不知道打開了多少個檔案描述符(包括socket句柄等),此時進行逐一清理確實有很大難度。我們期望的是能在fork子程序前開啟某個檔案句柄時就指定好:這個句柄我在fork子程序後執行exec時就關閉」。所以就有了 close-on-exec
- # 每個檔案描述符都有一個close-on-exec標誌。在系統預設情況下,這個標誌最後一位被設定為0。即關閉了此標誌。那麼當子進程呼叫exec函數,子進程將不會關閉該檔案描述子。此時,父子程序將共享該文件,它們具有同一個文件表項,也就有了同一個文件偏移量等。
-
fcntl()
的FD_CLOEXEC
和open()
的O_CLOEXEC
用來設定檔案的close-on-exec
,當close-on-exec標誌置為1時,即開啟此標誌, 此時子程序呼叫exec函數之前,系統就已經讓子程序將此檔案描述子關閉。
Note:雖然新版本支援在open時設定CLOEXEC,但在編譯的時候還是會提示錯誤 – error: ‘O_CLOEXEC’ undeclared (first use in this function)。這個功能需要設定巨集(_GNU_SOURCE)開啟。
#define _GNU_SOURCE //在源代码中加入 -D_GNU_SOURCE //在编译参数中加入
檔案狀態標誌(File Status Flag)
#File status flags 用來表示開啟檔案的屬性,file status flag可以透過duplicate一個檔案描述子來共用同一個開啟的檔案的狀態,而file descrptor flag則不行
- Access Modes: 指明檔案的access方式:read-only, write-only,read-write。透過open()設置,透過fcntl()返回,但不能被改變
- #Open-time Flags: 指明在open()執行的時候的操作,open()執行完畢這個flag不會被保存
- Operating Modes: 影響read,write操作,透過open()設置,但可以用fcntl()讀取或改變
open()
//给定一个文件路径名,按照相应的选项打开文件,就是将一个fd和文件连接到一起,成功返回文件描述符,失败返 回-1设errno #include int open(const char *pathname, int flags) int open(const char *pathname, int flags, mode_t mode)//不是函数重载,C中没有重载, 是可变长参数列 表 //pathname:文件或设备路径 //flags :file status flags=Access mode+Open-time flags+Operating Modes、 /*Access Mode(必选一个): O_RDONLY:0 O_WRONLY:1 O_RDWR:2 */ /*Open-time Flags(Bitwise Or): O_CLOEXEC :为新打开的文件描述符使能close-on-exec。可以避免程序再用fcntl()的F_SETFD来设置 FD_CLOEXEC O_CREAT :如果文件不存在就创建文件,并返回它的文件描述符,如果文件存在就忽略这个选项,必须在保护模式 下使用,eg:0664 O_DIRECTORY :如果opendir()在一个FIFO或tape中调用的话,这个选项可以避免denial-of-service问题, 如 果路径指向的不是一个目录,就会打开失败。 O_EXCL :确保open()能够穿件一个文件,如果文件已经存在,则会导致打开失败,总是和O_CREAT一同使用。 O_NOCTTY :如果路径指向一个终端设备,那么这个设备不会成为这个进程的控制终端,即使这个进程没有一个控制 终端 O_NOFOLLOW :如果路径是一个符号链接,就打开它链接的文 件//If pathname is a symbolic link, then the open fails. O_TMPFILE :创建一个无名的临时文件,文件系统中会创建一个无名的inode,当最后一个文件描述符被关闭的时 候,所有写入这个文件的内容都会丢失,除非在此之前给了它一个名字 O_TRUNC :清空文件 O_TTY_INIT */ /*Operating Modes(Bitwise Or) O_APPEND :以追加的方式打开文件, 默认写入结尾,在当下的Unix/Linux系统中,这个选项已经被定义为一个原 子操作 O_ASYNC :使能signal-driven I/O O_DIRECT :试图最小化来自I/O和这个文件的 cache effect//Try to minimize cache effects of the I/O to and from this file. O_DSYNC :每次写操作都会等待I/O操作的完成,但如果文件属性的更新不影响读取刚刚写入的数据的话,就不会 等待文件属性的更新 。 O_LARGEFILE :允许打开一个大小超过off_t(但没超过off64_t)表示范围的文件 O_NOATIME :不更改文件的st_time(last access time) O_NONBLOCK /O_NDELAY :如果可能的话,用nonblock模式打开文件 O_SYNC :每次写操作都会等待I/O操作的完成,包括write()引起的文件属性的更新。 O_PATH :获得一个能表示文件在文件系统中位置的文件描述符 #include #include int fd=open("b.txt",O_RDWR|O_CREAT|O_EXCL,0664); if(-1==fd) perror("open"),exit(-1);
FQ:Why Bitwise ORed:
FA:猜想有以下模型:用一串某一位是1其餘全是0的字串表示一個選項,選項作「位元與」就可得到0/1字串, 表示整個flags的狀態, Note: 低三位表示Access Mode
creat()
#等價於以O_WRONLY |O_TRUNC|O_CREAT的flag呼叫open()
#include int creat(const char *pathname, mode_t mode);
dup()、dup2()、dup3()
//复制一个文件描述符的指向,新的文件描述符的flags和原来的一样,成功返回new_file_descriptor, 失败返 回-1并设errno #include int dup(int oldfd); //使用未被占用的最小的文件描述符编号作为新的文件描述符 int dup2(int oldfd, int newfd); #include #include int dup3(int oldfd, int newfd, int flags); #include #include int res=dup2(fd,fd2); if(-1==res){ perror("dup2"),exit(-1);

read()
//从fd对应的文件中读count个byte的数据到以buf开头的缓冲区中,成功返回成功读取到的byte的数目,失败返回-1设errno #include ssize_t read(int fd, void *buf, size_t count); #include #include int res=read(fd,buf,6); if(-1==fd) perror("read"),exit(-1);
write()
//从buf指向的缓冲区中读取count个byte的数据写入到fd对应的文件中,成功返回成功写入的byte数目,文件的位置指针会向前移动这个数目,失败返回-1设errno #include ssize_t write(int fd, const void *buf, size_t count);//不需要对buf操作, 所以有const, VS read()没有const #include #include int res=write(fd,"hello",sizeof("hello")); if(-1==res) perror("write"),exit(-1);
Note: 上例中即使只有一个字符’A’,也要写”A”,因为”A”才是地址,’A’只是个int
lseek()
l 表示long int, 历史原因
//根据移动基准whence和移动距离offset对文件的位置指针进行重新定位,返回移动后的位置指针与文件开头的距离,失败返回-1设errno #include #include off_t lseek(int fd, off_t offset, int whence); /*whence: SEEK_SET:以文件开头为基准进行偏移,0一般不能向前偏 SEEK_CUR:以当前位置指针的位置为基准进行偏移,1向前向后均可 SEEK_END:以文件的结尾为基准进行偏移,2向前向后均可向后形成”文件空洞” #include #include int len=lseek(fd,-3,SEEK_SET); if(-1==len){ perror("lseek"),exit(-1);
fcntl()
//对fd进行各种操作,成功返回0,失败返回-1设errno #include #include int fcntl(int fd, int cmd, ... ); //...表示可变长参数 /*cmd: Adversory record locking: F_SETLK(struct flock*) //设建议锁 F_SETLKW(struct flock*) //设建议锁,如果文件上有冲突的锁,且在等待的时候捕获了一个信号,则调用被打断并在信号捕获之后立即返回一个错误,如果等待期间没有信号,则一直等待 F_GETLK(struct flock*) //尝试放锁,如果能放锁,则不会放锁,而是返回一个含有F_UNLCK而其他不变的l_type类型,如果不能放锁,那么fcntl()会将新类型的锁加在文件上,并把当前PID留在锁上 Duplicating a file descriptor: F_DUPFD (int) //找到>=arg的最小的可以使用的文件描述符,并把这个文件描述符用作fd的一个副本 F_DUPFD_CLOEXEC(int)//和F_DUPFD一样,除了会在新的文件描述符上设置close-on-exec F_GETFD (void) //读取fd的flag,忽略arg的值 F_SETFD (int) //将fd的flags设置成arg的值. F_GETFL (void) //读取fd的Access Mode和其他的file status flags; 忽略arg F_SETFL (long) //设置file status flags为arg F_GETOWN(void) //返回fd上接受SIGIO和SIGURG的PID或进程组ID F_SETOWN(int) //设置fd上接受SIGIO和SIGURG的PID或进程组ID为arg F_GETOWN_EX(struct f_owner_ex*) //返回当前文件被之前的F_SETOWN_EX操作定义的文件描述符R F_SETOWN_EX(struct f_owner_ex*) //和F_SETOWN类似,允许调用程序将fd的I/O信号处理权限直接交给一个线程,进程或进程组 F_GETSIG(void) //当文件的输入输出可用时返回一个信号 F_SETSIG(int) //当文件的输入输出可用时发送arg指定的信号 */ /*…: 可选参素,是否需要得看cmd,如果是加锁,这里应是struct flock* struct flock { short l_type; //%d Type of lock: F_RDLCK(读锁), F_WRLCK(写锁), F_UNLCK(解锁) short l_whence; //%d How to interpret l_start, 加锁的位置参考标准:SEEK_SET, SEEK_CUR, SEEK_END off_t l_start; //%ld Starting offset for lock, 加锁的起始位置 off_t l_len; //%ld Number of bytes to lock , 锁定的字节数 pid_t l_pid; // PID of process blocking our lock, (F_GETLK only)加锁的进程号,,默认给-1 }; */
建议锁(Adversory Lock)
限制加锁,但不限制读写, 所以只对加锁成功才读写的程序有效,用来解决不同的进程 同时对同一个文件的同一个位置 “写”导致的冲突问题
读锁是一把共享锁(S锁):共享锁+共享锁+共享锁+共享锁+共享锁+共享锁
写锁是一把排他锁(X锁):永远孤苦伶仃
释放锁的方法(逐级提高):
- 将锁的类型改为:F_UNLCK, 再使用fcntl()函数重新设置
- close()关闭fd时, 调用进程在该fd上加的所有锁都会自动释放
- 进程结束时会自动释放所有该进程加过的文件锁
Q:为什么加了写锁还能gedit或vim写???
A:可以写, 锁只可以控制能否加锁成功, 不能控制对文件的读写, 所以叫”建议”锁, 我加了锁就是不想让你写, 你非要写我也没办法. vim/gedit不通过能否加锁成功来决定是否读写, 所以可以直接上
Q: So如何实现文件锁控制文件的读写操作????
A:可以在读操作前尝试加读锁, 写操作前尝试加写锁, 根据能否加锁成功决定能否进行读写操作
int fd=open("./a.txt",O_RDWR); //得到fd if(-1==fd) perror("open"),exit(-1); struct flock lock={F_RDLCK,SEEK_SET,2,5,-1}; //设置锁 //此处从第3个byte开始(包含第三)锁5byte int res=fcntl(fd,F_SETLK,&lock); //给fd加锁 if(-1==res) perror("fcntl"),exit(-1);
ioct1()
这个函数可以实现其他文件操作函数所没有的功能,大多数情况下都用在设备驱动程序里,每个设备驱动程序可以定义自己专用的一组ioctl命令,系统则为不同种类的设备提供通用的ioctl命令
//操作特殊文件的设备参数,成功返回0,失败返回-1设errno #include int ioctl(int d, int request, ...); //d:an open file descriptor. //request: a device-dependent request code
close()
//关闭fd,这样这个fd就可以重新用于连接其他文件,成功返回0,失败返回-1设errno #include int close(int fd); #include #include int res=close(fd); if(-1==res) perror("close"),exit(-1);
通过本文,我们了解了Linux文件I/O的基本原理和方法,它们可以满足我们对文件的各种操作需求。我们应该根据实际需求选择合适的方法,并遵循一些基本原则,如关闭不用的文件描述符,检查错误返回值,使用合适的缓冲区大小等。文件I/O是Linux程序设计中不可或缺的一部分,它可以实现数据的持久化和交换,也可以提升程序的功能和性能。希望本文能够对你有所帮助和启发。
以上是Linux檔案I/O:原理與方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

Linux管理員的平均年薪在美國為75,000至95,000美元,歐洲為40,000至60,000歐元。提升薪資可以通過:1.持續學習新技術,如雲計算和容器技術;2.積累項目經驗並建立Portfolio;3.建立職業網絡,拓展人脈。

Linux的主要用途包括:1.服務器操作系統,2.嵌入式系統,3.桌面操作系統,4.開發和測試環境。 Linux在這些領域表現出色,提供了穩定性、安全性和高效的開發工具。

互聯網運行不依賴單一操作系統,但Linux在其中扮演重要角色。 Linux廣泛應用於服務器和網絡設備,因其穩定性、安全性和可擴展性受歡迎。

Linux操作系統的核心是其命令行界面,通過命令行可以執行各種操作。 1.文件和目錄操作使用ls、cd、mkdir、rm等命令管理文件和目錄。 2.用戶和權限管理通過useradd、passwd、chmod等命令確保系統安全和資源分配。 3.進程管理使用ps、kill等命令監控和控制系統進程。 4.網絡操作包括ping、ifconfig、ssh等命令配置和管理網絡連接。 5.系統監控和維護通過top、df、du等命令了解系統運行狀態和資源使用情況。

介紹 Linux是一個強大的操作系統,由於其靈活性和效率,開發人員,系統管理員和電源用戶都喜歡。但是,經常使用長而復雜的命令可能是乏味的

Linux適用於服務器、開發環境和嵌入式系統。 1.作為服務器操作系統,Linux穩定高效,常用於部署高並發應用。 2.作為開發環境,Linux提供高效的命令行工具和包管理系統,提升開發效率。 3.在嵌入式系統中,Linux輕量且可定制,適合資源有限的環境。

簡介:通過基於Linux的道德黑客攻擊數字邊界 在我們越來越相互聯繫的世界中,網絡安全至關重要。 道德黑客入侵和滲透測試對於主動識別和減輕脆弱性至關重要

Linux基礎學習從零開始的方法包括:1.了解文件系統和命令行界面,2.掌握基本命令如ls、cd、mkdir,3.學習文件操作,如創建和編輯文件,4.探索高級用法如管道和grep命令,5.掌握調試技巧和性能優化,6.通過實踐和探索不斷提陞技能。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

EditPlus 中文破解版
體積小,語法高亮,不支援程式碼提示功能

mPDF
mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver Mac版
視覺化網頁開發工具