Linux文件讀寫操作

来源:http://www.cnblogs.com/xiaojiang1025/archive/2016/10/06/5933755.html
-Advertisement-
Play Games

文件描述符(File Descriptor) a small, nonnegative integer for use in subsequent system calls (read(2), write(2), lseek(2), fcntl(2), etc.) ($man 2 open). 每個 ...


文件描述符(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_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<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);

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Linux中, 系統為每個系統都維護了三種計時器,分別為: 真實計數器, 虛擬計時器以及實用計時器, 一般情況下都使用真實計時器 getitimer()/setitimer() which //具體的計時器類型 1. ITIMER_REAL :真實計時器 統計進程消耗的真實時間 通過定時產生SIGA ...
  • 信號本質上就是一個軟體中斷,它既可以作為兩個進程間的通信的方式, 更重要的是, 信號可以終止一個正常程式的執行, 通常被用於處理意外情況 , 信號是非同步的, 也就是進程並不知道信號何時會到達 $kill 9 3390 向PID為3390的進程發送編號為9的信號= 一個兩個進程間通信的方式之一 一共6 ...
  • 環境:虛擬機VMware10 首先瞭解幾個註意的地方: 一、分區類型: 1、主分區:最多只能有四個; 2、擴展分區:最多只能有一個,且主分區加上擴展分區最多只能有四個,擴展分區不能寫入數據,只能包含邏輯分區 3、邏輯分區:可以寫入數據和格式化 舉個例子如圖: 其中1、2、3為主分區,4為擴展分區,5 ...
  • 向一個/一些進程發送一個信號 $kill [ slL] [...] 指定發送的信號,可以使用名稱或者信號編號 列出當前系統的所有信號 ...
  • 概述 多進程代碼區模型(其他區參見copy on write): getpid()、getppid() getuid()、geteuid() getgid(),getegid() fork() include include if(0==pid){ int res=execl("./proc","p ...
  • ps
    查看當前終端所啟動的進程, 不加選項只查看當前終端的進程 ps aux 查看所有進程,ps aux是BSD syntax,ps aux是standard syntax, 但二者的意義完全不同= $man ps ps ef 以全格式的方式顯示所有進程(every)查看當前終端所啟動的進程, 不加選項只 ...
  • 本文首先從巨集觀的角度對進程間的通信方式之一,消息隊列進行闡述,然後以代碼實例對消息隊列進行更近一步的闡述,最後試著暢想消息隊列的潛在應用 ...
  • access() fstat()、stat()、lstat() 獲取文件大小 1. fseek()把offset移到SEEK_END, 再用ftell()返迴文件的大小 2. lseek() , 返迴文件的大小 3.stat(), struct stat st; st.st_size的數值就是文件大 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...