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). 一個(gè)程序開始運(yùn)行時(shí)一般會(huì)有3個(gè)已經(jīng)打開的文件描述符:
- 0 :STDIN_FIFLENO,標(biāo)準(zhǔn)輸入stdin
- 1 :STDOUT_FILENO,標(biāo)準(zhǔn)輸出stdout
- 2 :STDERR_FILENO,標(biāo)準(zhǔn)錯(cuò)誤stderror
fd原理
- fd從0開始, 查找最小的未被使用的描述符, 把文件表指針與文件表描述符建立對(duì)應(yīng)關(guān)系(VS pid是一直向上漲,滿了再回來(lái)找)
- 文件描述符就是一個(gè)int, 用于代表一個(gè)打開的文件, 但是文件的管理信息不能夠不是存放在文件描述符中,當(dāng)使用open()函數(shù)打開一個(gè)文件時(shí), OS會(huì)將文件的相關(guān)信息加載到文件表等數(shù)據(jù)結(jié)構(gòu)中, 但出于安全和效率等因素的考慮, 文件表等數(shù)據(jù)結(jié)構(gòu)并不適合直接操作, 而是給該結(jié)構(gòu)指定一個(gè)編號(hào), 使用編號(hào)來(lái)進(jìn)行操作, 該編號(hào)就是文件描述符
- OS會(huì)為每個(gè)進(jìn)程內(nèi)部維護(hù)一張文件描述符總表, 當(dāng)有新的文件描述符需求時(shí), 會(huì)去總表中查找最小的未被使用的描述符返回, 文件描述符雖然是int類型, 但其實(shí)是非負(fù)整數(shù), 也就是0~OPEN_MAX(當(dāng)前系統(tǒng)中為1024), 其中0,1,2已被系統(tǒng)占用,分別表示stdin, stdout,stderror
- 使用close()關(guān)閉fd時(shí), 就是將fd和文件表結(jié)構(gòu)之間的對(duì)應(yīng)關(guān)系從總表中移除, 但不一定會(huì)刪除文件表結(jié)構(gòu), 只有當(dāng)文件表沒有與其他任何fd對(duì)應(yīng)時(shí)(也就是一個(gè)文件表可以同時(shí)對(duì)應(yīng)多個(gè)fd)才會(huì)刪除文件表, close()也不會(huì)改變文件描述符本身的整數(shù)值, 只會(huì)讓該文件描述符無(wú)法代表一個(gè)文件而已
- duplicate fdVS copy fd:dup是把old_fd對(duì)應(yīng)的文件表指針復(fù)制給new_fd, 而不是int new_fd=old_fd
- UNIX使用三種數(shù)據(jù)結(jié)構(gòu)描述打開的文件:每個(gè)進(jìn)程中用于描述當(dāng)前進(jìn)程打開文件的文件描述符表,表示當(dāng)前文件狀態(tài)的文件狀態(tài)標(biāo)識(shí)表,和用于找到文件i節(jié)點(diǎn)(索引節(jié)點(diǎn))的V節(jié)點(diǎn)表,Linux中并不使用這種Vnode結(jié)構(gòu),取而代之的是一種通用的inode結(jié)構(gòu),但本質(zhì)沒有區(qū)別,inode是在讀取文件時(shí)通過文件系統(tǒng)從磁盤中導(dǎo)入的文件位置
文件描述符標(biāo)志(File Descriptor Flag)
當(dāng)下的系統(tǒng)只有一個(gè)文件描述符標(biāo)志close-on-exec,僅僅是一個(gè)標(biāo)志,當(dāng)進(jìn)程fork一個(gè)子進(jìn)程的時(shí)候,在子進(jìn)程中調(diào)用了exec函數(shù)時(shí)就用到了該標(biāo)志。意義是執(zhí)行exec前是否要關(guān)閉這個(gè)文件描述符。
- 一般我們會(huì)調(diào)用exec執(zhí)行另一個(gè)程序,此時(shí)會(huì)用全新的程序替換子進(jìn)程的正文,數(shù)據(jù),堆和棧等。此時(shí)保存文件描述符的變量當(dāng)然也不存在了,我們就無(wú)法關(guān)閉無(wú)用的文件描述符了。所以通常我們會(huì)fork子進(jìn)程后在子進(jìn)程中直接執(zhí)行close關(guān)掉無(wú)用的文件描述符,然后再執(zhí)行exec。但是在復(fù)雜系統(tǒng)中,有時(shí)我們fork子進(jìn)程時(shí)已經(jīng)不知道打開了多少個(gè)文件描述符(包括socket句柄等),這此時(shí)進(jìn)行逐一清理確實(shí)有很大難度。我們期望的是能在fork子進(jìn)程前打開某個(gè)文件句柄時(shí)就指定好:這個(gè)句柄我在fork子進(jìn)程后執(zhí)行exec時(shí)就關(guān)閉”。所以就有了 close-on-exec
- 每個(gè)文件描述符都有一個(gè)close-on-exec標(biāo)志。在系統(tǒng)默認(rèn)情況下,這個(gè)標(biāo)志***一位被設(shè)置為0。即關(guān)閉了此標(biāo)志。那么當(dāng)子進(jìn)程調(diào)用exec函數(shù),子進(jìn)程將不會(huì)關(guān)閉該文件描述符。此時(shí),父子進(jìn)程將共享該文件,它們具有同一個(gè)文件表項(xiàng),也就有了同一個(gè)文件偏移量等。
- fcntl()的FD_CLOEXEC和open()的O_CLOEXEC用來(lái)設(shè)置文件的close-on-exec,當(dāng)將close-on-exec標(biāo)志置為1時(shí),即開啟此標(biāo)志, 此時(shí)子進(jìn)程調(diào)用exec函數(shù)之前,系統(tǒng)就已經(jīng)讓子進(jìn)程將此文件描述符關(guān)閉。
Note:雖然新版本支持在open時(shí)設(shè)置CLOEXEC,但是在編譯的時(shí)候還是會(huì)提示錯(cuò)誤 - error: ‘O_CLOEXEC’ undeclared (first use in this function)。這個(gè)功能需要設(shè)置宏(_GNU_SOURCE)打開。
- #define _GNU_SOURCE //在源代碼中加入
- -D_GNU_SOURCE //在編譯參數(shù)中加入
文件狀態(tài)標(biāo)志(File Status Flag)
File status flags 用來(lái)表示打開文件的屬性,file status flag可以通過duplicate一個(gè)文件描述符來(lái)共享同一個(gè)打開的文件的狀態(tài),而file descrptor flag則不行
- Access Modes: 指明文件的access方式:read-only, write-only,read-write。通過open()設(shè)置,通過fcntl()返回,但不能被改變
- Open-time Flags: 指明在open()執(zhí)行的時(shí)候的操作,open()執(zhí)行完畢這個(gè)flag不會(huì)被保存
- Operating Modes: 影響read,write操作,通過open()設(shè)置,但可以用fcntl()讀取或改變
open()
- //給定一個(gè)文件路徑名,按照相應(yīng)的選項(xiàng)打開文件,就是將一個(gè)fd和文件連接到一起,成功返回文件描述符,失敗返回-1設(shè)errno
- #include<fcntl.h>
- int open(const char *pathname, int flags)
- int open(const char *pathname, int flags, mode_t mode)
- //不是函數(shù)重載,C中沒有重載, 是可變長(zhǎng)參數(shù)列表
- //pathname:文件或設(shè)備路徑
- //flags :file status flags=Access mode+Open-time flags+Operating Modes、
- /*Access Mode(必選一個(gè)):
- O_RDONLY:0
- O_WRONLY:1
- O_RDWR:2
- */
- /*Open-time Flags(Bitwise Or):
- O_CLOEXEC :為新打開的文件描述符使能close-on-exec??梢员苊獬绦蛟儆胒cntl()的F_SETFD來(lái)設(shè)置FD_CLOEXEC
- O_CREAT :如果文件不存在就創(chuàng)建文件,并返回它的文件描述符,如果文件存在就忽略這個(gè)選項(xiàng),必須在保護(hù)模式下使用,eg:0664
- O_DIRECTORY :如果opendir()在一個(gè)FIFO或tape中調(diào)用的話,這個(gè)選項(xiàng)可以避免denial-of-service問題, 如果路徑指向的不是一個(gè)目錄,就會(huì)打開失敗。
- O_EXCL :確保open()能夠穿件一個(gè)文件,如果文件已經(jīng)存在,則會(huì)導(dǎo)致打開失敗,總是和O_CREAT一同使用。
- O_NOCTTY :如果路徑指向一個(gè)終端設(shè)備,那么這個(gè)設(shè)備不會(huì)成為這個(gè)進(jìn)程的控制終端,即使這個(gè)進(jìn)程沒有一個(gè)控制終端
- O_NOFOLLOW :如果路徑是一個(gè)符號(hào)鏈接,就打開它鏈接的文件//If pathname is a symbolic link, then the open fails.
- O_TMPFILE :創(chuàng)建一個(gè)無(wú)名的臨時(shí)文件,文件系統(tǒng)中會(huì)創(chuàng)建一個(gè)無(wú)名的inode,當(dāng)***一個(gè)文件描述符被關(guān)閉的時(shí)候,所有寫入這個(gè)文件的內(nèi)容都會(huì)丟失,除非在此之前給了它一個(gè)名字
- O_TRUNC :清空文件
- O_TTY_INIT
- *//*Operating Modes(Bitwise Or)
- O_APPEND :以追加的方式打開文件, 默認(rèn)寫入結(jié)尾,在當(dāng)下的Unix/Linux系統(tǒng)中,這個(gè)選項(xiàng)已經(jīng)被定義為一個(gè)原子操作
- O_ASYNC :使能signal-driven I/O
- O_DIRECT :試圖最小化來(lái)自I/O和這個(gè)文件的cache effect//Try to minimize cache effects of the I/O to and from this file.
- O_DSYNC :每次寫操作都會(huì)等待I/O操作的完成,但如果文件屬性的更新不影響讀取剛剛寫入的數(shù)據(jù)的話,就不會(huì)等待文件屬性的更新 。
- O_LARGEFILE :允許打開一個(gè)大小超過off_t(但沒超過off64_t)表示范圍的文件
- O_NOATIME :不更改文件的st_time(last access time)
- O_NONBLOCK /O_NDELAY :如果可能的話,用nonblock模式打開文件
- O_SYNC :每次寫操作都會(huì)等待I/O操作的完成,包括write()引起的文件屬性的更新。
- O_PATH :獲得一個(gè)能表示文件在文件系統(tǒng)中位置的文件描述符
- #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);
FA:猜想有以下模型:用一串某一位是1其余全是0的字符串表示一個(gè)選項(xiàng), 選項(xiàng)們作 “按位與”就可得到0/1字符串, 表示整個(gè)flags的狀態(tài), Note: 低三位表示Access Mode
creat()
等價(jià)于以O(shè)_WRONLY |O_TRUNC|O_CREAT的flag調(diào)用open()
- #include<fcntl.h>
- int creat(const char *pathname, mode_t mode);
dup()、dup2()、dup3()
- /復(fù)制一個(gè)文件描述符的指向,新的文件描述符的flags和原來(lái)的一樣,成功返回new_file_descriptor, 失敗返回-1并設(shè)errno
- #include <unistd.h>
- int dup(int oldfd); //使用未被占用的最小的文件描述符編號(hào)作為新的文件描述符
- 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對(duì)應(yīng)的文件中讀count個(gè)byte的數(shù)據(jù)到以buf開頭的緩沖區(qū)中,成功返回成功讀取到的byte的數(shù)目,失敗返回-1設(shè)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指向的緩沖區(qū)中讀取count個(gè)byte的數(shù)據(jù)寫入到fd對(duì)應(yīng)的文件中,成功返回成功寫入的byte數(shù)目,文件的位置指針會(huì)向前移動(dòng)這個(gè)數(shù)目,失敗返回-1設(shè)errno
- #include <unistd.h>
- ssize_t write(int fd, const void *buf, size_t count);//不需要對(duì)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: 上例中即使只有一個(gè)字符’A’,也要寫”A”,因?yàn)?rdquo;A”才是地址,’A’只是個(gè)int
lseek()
l 表示long int, 歷史原因
- //根據(jù)移動(dòng)基準(zhǔn)whence和移動(dòng)距離offset對(duì)文件的位置指針進(jìn)行重新定位,返回移動(dòng)后的位置指針與文件開頭的距離,失敗返回-1設(shè)errno
- #include <unistd.h>
- #include <sys/types.h>
- off_t lseek(int fd, off_t offset, int whence);
- /*whence:
- SEEK_SET:以文件開頭為基準(zhǔn)進(jìn)行偏移,0一般不能向前偏
- SEEK_CUR:以當(dāng)前位置指針的位置為基準(zhǔn)進(jìn)行偏移,1向前向后均可
- SEEK_END:以文件的結(jié)尾為基準(zhǔn)進(jìn)行偏移,2向前向后均可向后形成”文件空洞”
- #include<unistd.h>
- #include<stdlib>
- int len=lseek(fd,-3,SEEK_SET);
- if(-1==len){
- perror("lseek"),exit(-1);
- }
fcntl()
- //對(duì)fd進(jìn)行各種操作,成功返回0,失敗返回-1設(shè)errno
- #include <unistd.h>
- #include <fcntl.h>
- int fcntl(int fd, int cmd, ... ); //...表示可變長(zhǎng)參數(shù)
- /*cmd:
- Adversory record locking:
- F_SETLK(struct flock*) //設(shè)建議鎖
- F_SETLKW(struct flock*) //設(shè)建議鎖,如果文件上有沖突的鎖,且在等待的時(shí)候捕獲了一個(gè)信號(hào),則調(diào)用被打斷并在信號(hào)捕獲之后立即返回一個(gè)錯(cuò)誤,如果等待期間沒有信號(hào),則一直等待
- F_GETLK(struct flock*) //嘗試放鎖,如果能放鎖,則不會(huì)放鎖,而是返回一個(gè)含有F_UNLCK而其他不變的l_type類型,如果不能放鎖,那么fcntl()會(huì)將新類型的鎖加在文件上,并把當(dāng)前PID留在鎖上
- Duplicating a file descriptor:
- F_DUPFD (int) //找到>=arg的最小的可以使用的文件描述符,并把這個(gè)文件描述符用作fd的一個(gè)副本
- F_DUPFD_CLOEXEC(int)//和F_DUPFD一樣,除了會(huì)在新的文件描述符上設(shè)置close-on-execF_GETFD (void) //讀取fd的flag,忽略arg的值
- F_SETFD (int) //將fd的flags設(shè)置成arg的值.
- F_GETFL (void) //讀取fd的Access Mode和其他的file status flags; 忽略arg
- F_SETFL (long) //設(shè)置file status flags為arg
- F_GETOWN(void) //返回fd上接受SIGIO和SIGURG的PID或進(jìn)程組ID
- F_SETOWN(int) //設(shè)置fd上接受SIGIO和SIGURG的PID或進(jìn)程組ID為arg
- F_GETOWN_EX(struct f_owner_ex*) //返回當(dāng)前文件被之前的F_SETOWN_EX操作定義的文件描述符R
- F_SETOWN_EX(struct f_owner_ex*) //和F_SETOWN類似,允許調(diào)用程序?qū)d的I/O信號(hào)處理權(quán)限直接交給一個(gè)線程,進(jìn)程或進(jìn)程組
- F_GETSIG(void) //當(dāng)文件的輸入輸出可用時(shí)返回一個(gè)信號(hào)
- F_SETSIG(int) //當(dāng)文件的輸入輸出可用時(shí)發(fā)送arg指定的信號(hào)
- */
- /*…:
- 可選參素,是否需要得看cmd,如果是加鎖,這里應(yīng)是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, 加鎖的位置參考標(biāo)準(zhǔn):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 , 鎖定的字節(jié)數(shù)
- pid_t l_pid; // PID of process blocking our lock, (F_GETLK only)加鎖的進(jìn)程號(hào),,默認(rèn)給-1};
- */
建議鎖(Adversory Lock)
限制加鎖,但不限制讀寫, 所以只對(duì)加鎖成功才讀寫的程序有效,用來(lái)解決不同的進(jìn)程 同時(shí)對(duì)同一個(gè)文件的同一個(gè)位置 “寫”導(dǎo)致的沖突問題
讀鎖是一把共享鎖(S鎖):共享鎖+共享鎖+共享鎖+共享鎖+共享鎖+共享鎖
寫鎖是一把排他鎖(X鎖):永遠(yuǎn)孤苦伶仃
釋放鎖的方法(逐級(jí)提高):
- 將鎖的類型改為:F_UNLCK, 再使用fcntl()函數(shù)重新設(shè)置
- close()關(guān)閉fd時(shí), 調(diào)用進(jìn)程在該fd上加的所有鎖都會(huì)自動(dòng)釋放
- 進(jìn)程結(jié)束時(shí)會(huì)自動(dòng)釋放所有該進(jìn)程加過的文件鎖
Q:為什么加了寫鎖還能gedit或vim寫???
A:可以寫, 鎖只可以控制能否加鎖成功, 不能控制對(duì)文件的讀寫, 所以叫”建議”鎖, 我加了鎖就是不想讓你寫, 你非要寫我也沒辦法. vim/gedit不通過能否加鎖成功來(lái)決定是否讀寫, 所以可以直接上
Q: So如何實(shí)現(xiàn)文件鎖控制文件的讀寫操作????
A:可以在讀操作前嘗試加讀鎖, 寫操作前嘗試加寫鎖, 根據(jù)能否加鎖成功決定能否進(jìn)行讀寫操作
- 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}; //設(shè)置鎖 //此處從第3個(gè)byte開始(包含第三)鎖5byte
- int res=fcntl(fd,F_SETLK,&lock); //給fd加鎖
- if(-1==res)
- perror("fcntl"),exit(-1);
ioct1()
這個(gè)函數(shù)可以實(shí)現(xiàn)其他文件操作函數(shù)所沒有的功能,大多數(shù)情況下都用在設(shè)備驅(qū)動(dòng)程序里,每個(gè)設(shè)備驅(qū)動(dòng)程序可以定義自己專用的一組ioctl命令,系統(tǒng)則為不同種類的設(shè)備提供通用的ioctl命令
- //操作特殊文件的設(shè)備參數(shù),成功返回0,失敗返回-1設(shè)errno
- #include <sys/ioctl.h>
- int ioctl(int d, int request, ...);
- //d:an open file descriptor.//request: a device-dependent request code
close()
- //關(guān)閉fd,這樣這個(gè)fd就可以重新用于連接其他文件,成功返回0,失敗返回-1設(shè)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);