2021-05-12 14:32:11
Linux檔案操作詳述
檔案描述符(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
- 1 :標準輸出stdout
- 2 :標準錯誤stderror
fd工作原理
- fd從0開始, 查詢最小的未被使用的描述符, 把檔案表指標與檔案表描述符建立對應關係
- 檔案描述符就是一個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
檔案描述符標誌(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<fcntl.h>
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 :以追加的方式開啟檔案, 預設寫入結尾
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<fcntl.h>
#include<stdlib.h>
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<fcntl.h>
int creat(const char *pathname, mode_t mode);
dup()、dup2()、dup3()
//複製一個檔案描述符的指向,新的檔案描述符的flags和原來的一樣,成功返回new_file_descriptor, 失敗返回-1並設errno
#include <unistd.h>
int dup(int oldfd); //使用未被佔用的最小的檔案描述符編號作為新的檔案描述符
int dup2(int oldfd, int newfd);
#include <fcntl.h>
#include <unistd.h>
int dup3(int oldfd, int newfd, int flags);
#include<unistd.h>
#include<stdlib.h>
int res=dup2(fd,fd2);
if(-1==res){
perror("dup2"),exit(-1);
read()
//從fd對應的檔案中讀count個byte的資料到以buf開頭的緩衝區中,成功返回成功讀取到的byte的數目,失敗返回-1設errno
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
#include <unistd.h>
#include<stdlib.h>
int res=read(fd,buf,6);
if(-1==fd)
perror("read"),exit(-1);
write()
//從buf指向的緩衝區中讀取count個byte的資料寫入到fd對應的檔案中,成功返回成功寫入的byte數目,檔案的位置指標會向前移動這個數目,失敗返回-1設errno
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);//不需要對buf操作, 所以有const, VS read()沒有const
#include <unistd.h>
#include<stdlib.h>
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 <unistd.h>
#include <sys/types.h>
off_t lseek(int fd, off_t offset, int whence);
/*whence:
SEEK_SET:以檔案開頭為基準進行偏移,0一般不能向前偏
SEEK_CUR:以當前位置指標的位置為基準進行偏移,1向前向後均可
SEEK_END:以檔案的結尾為基準進行偏移,2向前向後均可?向後形成”檔案空洞”
#include<unistd.h>
#include<stdlib>
int len=lseek(fd,-3,SEEK_SET);
if(-1==len){
perror("lseek"),exit(-1);
fcntl()
//對fd進行各種操作,成功返回0,失敗返回-1設errno
#include <unistd.h>
#include <fcntl.h>
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()
//操作特殊檔案的裝置引數,成功返回0,失敗返回-1設errno
#include <sys/ioctl.h>
int ioctl(int d, int request, ...);
//d:an open file descriptor.
//request: a device-dependent request code
close()
//關閉fd,這樣這個fd就可以重新用於連線其他檔案,成功返回0,失敗返回-1設errno
#include <unistd.h>
int close(int fd);
#include <unistd.h>
#include<stdlib.h>
int res=close(fd);
if(-1==res)
perror("close"),exit(-1);
本文永久更新連結地址:http://www.linuxidc.com/Linux/2016-10/135728.htm
相關文章