linux程式設計--open、write、lseek


Posted by nathan2009729 on 2022-07-02

程式1:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main()  //  0   1    2
{ 
  ssize_t WRLen;
  int FD = open ( "C2.c" , O_RDWR |O_APPEND );

  printf( "FD=%d\r\n", FD );   // 3    ---> write to 1

  WRLen = write ( 1 , (void *)"Hi CYH\r\n", 8) ;

  WRLen = write ( FD , (void *)"Hi CYH\r\n", 8) ;


  close(1);
  printf( "Hello CYH\r\n"); 
  WRLen = write ( 1 , (void *)"Hi_CYH\r\n", 8) ;

  close( FD );

  return 0;
}

FD:文件描述符,每多開一個文件就加1
file descriptor (fd) 基本上是一層介面,可以讓我們去操作 file 和其他 input/output interface (例如 pipe & socket)。每个进程都会预留3个默认的fd: STDIN_FILENO(stdin)、STDOUT_FILENO(stdout)、STDERR_FILENO(stderr);它们的值分别是0、1,2。
程式1裡新開文件從3開始,以上程式演示了開啟跟寫入。

開啟

(引用https://blog.jaycetyle.com/2018/12/linux-fd-open-close/)
存取檔案的操作都會需要 fd,而 fd 的取得是透過 open() 系統呼叫。open() 系統呼叫有以下兩種形式,其回傳的 int 變數就是 fd:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *name, int flags);
int open(const char *name, int flags, mode_t mode);

open() 的 flags 可以是一個或多個值 OR 的結果,用以表示開啟要求的行為,且必須包含 O_RDONLY、O_WRONLY 或 O_RDWR 三者其中之一,但如果開檔的行程不具備對應的操作權限,當然也就不能以該模式開啟檔案。
必要旗標

  • O_RDONLY: 以唯讀模式開啟
  • O_WRONLY: 以唯寫模式開啟
  • O_RDWR: 以讀寫模式開啟
    行為操作旗標
  • O_APPEND: 以附加模式開啟
    以附加模式開啟的檔案,寫入操作都會從檔案末端開始,就算第二個行程也對該檔案進行寫入因而改變了檔案末端位置
    例如多個行程要對相同 log 檔進行寫入的應用時,這個模式就會相當好用,因為它可以避免多個寫入者互相競爭的情況
  • O_CLOEXEC: 在執行 execl 後自動關閉該 fd
    fork() 後執行 execl() 是常見的操作,不過 fork() 的子行程會複製父行程的 fd
    execl() 時通常都不想保留 fd,設立此 flags 的 fd 可以在執行 execl 時自動關閉
  • O_NOATIME: 進行讀取操作時不更新檔案的存取時間
    對於檢索、備份類會經常大量讀取系統上所有檔案的程式有很大的幫助,可以減少讀取後要更新 inode 造成的寫入量
  • O_TRUNC: 若開啟的檔案存在且是一般檔案,開啟後就將其截短成長度為 0
    以 O_RDONLY | O_TRUNC 開啟檔案是未定義行為
    同步、非同步旗標
  • O_SYNC: 以 SYNC 模式開啟檔案 (之後會再補充同步 IO)
  • O_ASYNC: 如果指定的檔案變成可讀或可寫的狀態,就產生一個 Signal (預設為 SIGIO)
    用於 Socket、FIFO、Terminal 等需要非同步操作的情境
    不能用於一般檔案
    比較常見的應該是使用 aio 相關函式庫而不是使用 O_ASYNC 模式
  • O_NONBLOCK: 以 non-blocking IO 模式開啟
    對 FIFO 做 read() 時,如果沒有資料可以讀取時立即返回(不阻擋)
    因為沒資料而返回時會將 errno 設為 EAGAIN
  • O_DIRECT: 以 Direct IO 模式開啟檔案 (之後會再補充 Direct IO)
    建檔相關旗標
  • O_CREAT: 如果指定的檔案不存在,就建立一個
  • O_EXCL: 有指定 O_CREAT 時才有效。如果開檔時檔案已經存在,就回傳失敗,可以避免兩個行程想同時建檔的競爭情況
    可能比較少用的旗標
  • O_DIRECTORY: 如果開的檔案不是一個目錄就回傳失敗
  • O_LARGEFILE: 採用 64 位元 Offset 開啟檔案,可開啟 2GB 以上大檔,在 64 位元系統是預設值
  • O_NOCTTY: 和終端機相關
  • O_NOFOLLOW: 如果開啟的檔案是 symbolic link,就開啟失敗,但開啟的檔案所屬的目錄是 symbolic link 的話仍然會成功

新檔案的使用權限
  新檔案的使用權限是由 open 的第三個引數 umode_t mode 做指定。如果沒帶 O_CREAT 旗標,該引數會被忽略,但如果有 O_CREAT 旗標但卻沒有指定 mode 的話,行為會是不明確的。mode 引數就是一般的 Unix 權限位元設定,可以使用 POSIX 預定義的幾個常數用 OR 運算結合指定:

  • S_IRUSR: User 擁有 r 權限
  • S_IWUSR: User 擁有 w 權限
  • S_IXUSR: User 擁有 x 權限
  • S_IRWXU: S_IRUSR | S_IWUSR | S_IXUSR
  • S_IRGRP: Group 擁有 r 權限
  • S_IWGRP: Group 擁有 w 權限
  • S_IXGRP: Group 擁有 x 權限
  • S_IRWXG: S_IRGRP | S_IWGRP | S_IXGRP
  • S_IROTH: Other 擁有 r 權限
  • S_IWOTH: Other 擁有 w 權限
  • S_IXOTH: Other 擁有 x 權限
  • S_IRWXO: S_IROTH | S_IWOTH | S_IXOTH
      實際上儲存的權限還會受到 umask 影響,是一個行程屬性,他會遮蔽 mode 中對應的權限位元,例如 022 的 mask 會把 0666 的權限變成 0644。相關的處理可以在核心 namei.c 的 lookup_open() 中找到。只要講到行程屬性,就有很大的機會會在核心 linux/sched.h 的 task_struct ,而他實際上也是在那裡,位於 task_struct 中的 fs_struct *fs 結構內,umask 系統呼叫也是直接修改該值。

所以以上程式中

int FD = open ( "C2.c" , O_RDWR |O_APPEND );

表示打開一個可供讀寫的C2.c,且寫入到文件底部。

寫入

(引用自https://blog.jaycetyle.com/2019/01/linux-read-write/)
write函式寫入參數如下:

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

 write() 系統呼叫會從 buf 中把 count 位元組的資料寫入 fd 所參照檔案的當前位置,執行成功時會回傳寫進入檔案的位元組數,檔案位置也會前進寫入的位元組數,執行失敗時會回傳 -1 並設定 errno。write() 不像 read() 會遇到 EOF,比較不容易發生回傳值不等於 count,但如果 write 的對象是 socket 的話,就仍然要處理只有部分寫入的情況 (回傳小於 count 但大於等於 0)。

所以上述程式中

WRLen = write ( 1 , (void *)"Hi CYH\r\n", 8) ;  
WRLen = write ( FD , (void *)"Hi CYH\r\n", 8) ;

是分別寫到標準輸出跟C2.c了。

關閉

close() 系統呼叫
  應用程式使用完 fd 後,可以用 close() 系統呼叫將 fd 從檔案表中移出,核心主要的實作函式為 fs/file.c 內的 __close_fd()。另外,在 fd 回收完成後,核心也會呼叫檔案的 flush()。

程式2

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

int main(int argc, char* argv[] )  //  0   1    2
{ // cyhCP  SRCFile  TargFILE[enter]
  if( argc == 3 )
  {  char readBUF[ 64 ];  ssize_t readLen;
      int SRCFD =  open (  argv[1], O_RDWR );
      int targFD = open (  argv[2], O_RDWR  | O_CREAT | O_TRUNC ); 

      while( (readLen = read ( SRCFD , (void*)readBUF, 64 ) ) > 0 )
         write (targFD, (void*)readBUF , readLen );

      close ( SRCFD ) ;
      close ( targFD );
  }
  else
     printf( "USAGE: cyhCP  SRCFile  TargFILE[enter]\r\n" );

  return 0;
}

以上程式可以拿來複製文件,值得注意的是
argv[1]->執行的第一個參數
argv[2]->執行的第二個參數

程式3

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

int main(int argc, char* argv[] )  //  0   1    2
{ // cyhCP  SRCFile  TargFILE[enter]
  if( argc == 3 )
  {  char readBUF;   ssize_t readLen;
      int SRCFD =  open (  argv[1], O_RDWR );
      int targFD = open (  argv[2], O_RDWR  | O_CREAT | O_TRUNC ); 

      while( (readLen = read ( SRCFD , (void*)&readBUF, 1 ) ) > 0 )
      {
         write (targFD, (void*)&readBUF , readLen );
          lseek ( SRCFD , 1, SEEK_CUR );
      }
      close ( SRCFD ) ;
      close ( targFD );
  }
  else
     printf( "USAGE: cyhCP  SRCFile  TargFILE[enter]\r\n" );

  return 0;
}

lseek

(引用自https://blog.jaycetyle.com/2019/01/linux-read-write/)

#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fd, off_t pos, int origin);

lseek() 系統呼叫用來設定檔案的當前位置,本身不會產生任何 IO,成功時會回傳檔案的當前位置,失敗時回傳 -1 並設定 errn。lseek() 的 pos 可以是正、負或是零,實際的行為取決於 origin,可以是以下幾種的其中一個:

  • SEEK_CUR: 位置設定為當前位置加上 pos
    pos 是0時可以用來查詢目前檔案位置
  • SEEK_END: 位置設定為當前的長度再加上 pos,也就是從結尾開始算的意思
    pos 是0時可以把當前位置設為檔案結尾
  • SEEK_SET: 位置設定為 pos,也就是從開頭開始算的意思
    pos 是0時可以把當前位置設為檔案開頭
      lseek() 也可以把檔案當前位置設定到檔案結尾以後。此時如果進行讀取的話,會回傳 EOF,若進行寫入的話,中間被跳過的部分會以 hole 的形式填補成 0 而變成 sparse file。

  另外 Linux 還有提供了結合 read()、write() 及 lssek() 的變體,他們會從 pos 的位置做讀取或寫入,且完成後不會更動檔案的目前位置,且他也可以避免多執行緒情境下先做 lseek() 再做 read()、write() 可能的 race 情況。

#define _XOPEN_SOURCE 500
#include <unistd.h>

ssize_t pread(int fd, void *buf, size_t count);
ssize_t pwrite(int fd, const void *buf, size_t count);

#read #write #lseek







Related Posts

初學 ESLint

初學 ESLint

VS Code Settings

VS Code Settings

C# 各種類型轉換

C# 各種類型轉換


Comments