目錄
#檔案描述子(File Descriptor)" >#檔案描述子(File Descriptor)
#fd原理" >#fd原理
檔案描述標誌(File Descriptor Flag)" >檔案描述標誌(File Descriptor Flag)
檔案狀態標誌(File Status Flag)" >檔案狀態標誌(File Status Flag)
open()" >open()
creat()" >creat()
dup()、dup2()、dup3()" >dup()、dup2()、dup3()
read()" >read()
write()" >write()
lseek()" >lseek()
fcntl()" >fcntl()
建议锁(Adversory Lock)" >建议锁(Adversory Lock)
释放锁的方法(逐级提高):" >释放锁的方法(逐级提高):
ioct1()" >ioct1()
close()" >close()
首頁 系統教程 Linux Linux檔案I/O:原理與方法

Linux檔案I/O:原理與方法

Feb 09, 2024 pm 06:27 PM
linux linux教程 linux系統 linux指令 shell腳本 嵌入式linux linux入門 linux學習

文件是Linux系統中最基本和最常用的資料儲存方式,它們可以是文字文件,二進位文件,設備文件,目錄文件等。檔案的讀寫是Linux程式設計中最重要的操作之一,它涉及到檔案描述符,緩衝區,系統調用,庫函數等概念。在本文中,我們將介紹Linux檔案I/O的基本原理和方法,包括打開,關閉,讀取,寫入,定位,截斷,同步等操作,並舉例說明它們的使用方法和注意事項。

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是在讀取檔案時透過檔案系統從磁碟匯入的檔案位置
    Linux檔案I/O:原理與方法
Linux檔案I/O:原理與方法

檔案描述標誌(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_CLOEXECopen()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);
登入後複製
Linux檔案I/O:原理與方法

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中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1673
14
CakePHP 教程
1429
52
Laravel 教程
1333
25
PHP教程
1278
29
C# 教程
1257
24
Linux體系結構:揭示5個基本組件 Linux體系結構:揭示5個基本組件 Apr 20, 2025 am 12:04 AM

Linux系統的五個基本組件是:1.內核,2.系統庫,3.系統實用程序,4.圖形用戶界面,5.應用程序。內核管理硬件資源,系統庫提供預編譯函數,系統實用程序用於系統管理,GUI提供可視化交互,應用程序利用這些組件實現功能。

git怎麼查看倉庫地址 git怎麼查看倉庫地址 Apr 17, 2025 pm 01:54 PM

要查看 Git 倉庫地址,請執行以下步驟:1. 打開命令行並導航到倉庫目錄;2. 運行 "git remote -v" 命令;3. 查看輸出中的倉庫名稱及其相應的地址。

notepad怎麼運行java代碼 notepad怎麼運行java代碼 Apr 16, 2025 pm 07:39 PM

雖然 Notepad 無法直接運行 Java 代碼,但可以通過借助其他工具實現:使用命令行編譯器 (javac) 編譯代碼,生成字節碼文件 (filename.class)。使用 Java 解釋器 (java) 解釋字節碼,執行代碼並輸出結果。

sublime寫好代碼後如何運行 sublime寫好代碼後如何運行 Apr 16, 2025 am 08:51 AM

在 Sublime 中運行代碼的方法有六種:通過熱鍵、菜單、構建系統、命令行、設置默認構建系統和自定義構建命令,並可通過右鍵單擊項目/文件運行單個文件/項目,構建系統可用性取決於 Sublime Text 的安裝情況。

Linux的主要目的是什麼? Linux的主要目的是什麼? Apr 16, 2025 am 12:19 AM

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

laravel安裝代碼 laravel安裝代碼 Apr 18, 2025 pm 12:30 PM

要安裝 Laravel,需依序進行以下步驟:安裝 Composer(適用於 macOS/Linux 和 Windows)安裝 Laravel 安裝器創建新項目啟動服務訪問應用程序(網址:http://127.0.0.1:8000)設置數據庫連接(如果需要)

git軟件安裝 git軟件安裝 Apr 17, 2025 am 11:57 AM

安裝 Git 軟件包括以下步驟:下載安裝包運行安裝包驗證安裝配置 Git安裝 Git Bash(僅限 Windows)

如何設置重要的 Git 配置全局屬性 如何設置重要的 Git 配置全局屬性 Apr 17, 2025 pm 12:21 PM

自定義開發環境的方法有很多種,但全局 Git 配置文件是最有可能用於自定義設置(例如用戶名、電子郵件、首選文本編輯器和遠程分支)的一種。以下是您需要了解的有關全局 Git 配置文件的關鍵事項。

See all articles