1. 程式人生 > >linux系統呼叫open、write、close、read以及stat函式詳解

linux系統呼叫open、write、close、read以及stat函式詳解

學習筆記 參考連結1參考連結2以及百度百科
在進行C語言學習的時候我們瞭解到了C語言相關的一些IO操作,如fopen,fwrite,fread,fprintf,fclose等相關函式,他們都是由C庫函式提供的一些函式,是將作業系統的系統呼叫加以封裝,雖說Linux是由C語言實現的,但為了使我們更加的瞭解Linux,就需要了解更接近與底層的一些IO操作,因此就需要來了解下基本的系統呼叫—open,write,read,close
首先我們來了解下open,write,read,close的系統呼叫

open

#include <sys/types.h>
#include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags, mode_t mode);

open有三個引數
pathname:要開啟或建立的目標檔名
flags:對檔案進行多種操作也就有有多個引數,這多個引數可以進行或運算,即就是flags
引數:
O_RDONLY:只讀開啟
O_WRONLY:只寫開啟
O_RDWR:讀,寫開啟
O_CREAT:若檔案不存在,建立檔案
O_APPEND:追加寫
引數1,2,3,必須制定一個且只能制定一個,使用引數4,必須使用open的第三個引數mode:新檔案的訪問許可權
返回值:成功:新開啟檔案的檔案描述符(fd)
失敗:-1

write

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

fd:檔案描述符
buf:寫入的緩衝區
count:寫的字元長度,也就是看你需要寫多少
返回值:
如果順利write()會返回實際寫入的位元組數。當有錯誤發生時則返回-1,錯誤程式碼存入errno中。
read

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

fd:檔案描述符
buf:讀入的緩衝區
count:寫的字元長度,也就是看你需要寫多少
返回值
如果順利read()會返回實際寫入的位元組數。當有錯誤發生時則返回-1,錯誤程式碼存入errno中。
close

#include <unistd.h>
int close(int fd);

close的引數就相對簡單了,這一個操作是不能遺漏的,只要了使用fd就必須close它
在這幾個函式中都涉及到了關鍵的引數fd,因此要了解這幾個函式,就必須先了解下檔案描述符(fd)。
什麼是檔案描述符,這是一個相對抽象的概念,我們先來看看下面這張圖
這裡寫圖片描述
在PCB結構體中存在一個files指標,它指向一個file_struct結構體,而在file_struct結構體中存在一個file* fd陣列,這個數組裡面存放的是file指標,用來指向不同的file檔案,而fd就可以理解為這個指標陣列的下標,因此要開啟一個檔案,我們就可以拿到該檔案的fd就可以了。
fd的分配原則:
files_struct陣列當中,使用沒有被使用的最小下標,作為新的檔案描述符。
作業系統預設使用了該陣列的前三個元素,0號下標指向標準輸入(stdin),1號下標指向標準輸出(stdout),2號下標指向標準錯誤(stderr)。
因此正常情況下,新的fd都是從3開始的,但如果我們關閉了預設的fd,新的檔案的fd就從關閉的fd處開始。
說到了fd,我們就不得不來區分下FILEfd
FILE是C庫當中提供的一個結構體,而fd是系統呼叫,更加接近於底層,因此FILE中必定封裝了fd。
我們可以來看看FILE的結構體:
typedef struct _IO_FILE FILE;在/usr/include/stdio.h
它的結構體中有這麼一段

struct _IO_FILE {
  int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

//緩衝區相關
  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr;   /* Current read pointer */
  char* _IO_read_end;   /* End of get area. */
  char* _IO_read_base;  /* Start of putback+get area. */
  char* _IO_write_base; /* Start of put area. */
  char* _IO_write_ptr;  /* Current put pointer. */
  char* _IO_write_end;  /* End of put area. */
  char* _IO_buf_base;   /* Start of reserve area. */
  char* _IO_buf_end;    /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */ 
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;//fd的封裝

可以看到int_fileno就是對fd的封裝,在這一部分的開頭有一大段跟緩衝區相關的內容,為什麼要諾列出它呢,首先可以來看一個很詭異的例子:

#include <stdio.h>                                                            
  #include <string.h>
  #include <unistd.h>
  #include <sys/stat.h>
  #include <sys/types.h>
  #include <fcntl.h>

  int main(){
      const char *msg1 = "hello printf\n";
      const char *msg2 = "hello fwrite\n";
      const char *msg3 = "hello write\n";

      printf(msg1);
      fwrite(msg2, 1, strlen(msg2), stdout);
      write(1, msg3, strlen(msg3));
      fork();
      return 0;
  }

執行結果:

[[email protected] test]$ ./hello
hello printf 
hello fwrite 
hello write

但當我們對程序實現輸出重定向,你就會發現詭異的事情:

[[email protected] test]$ ./hello > file
[[email protected] test]$ cat file 
hello write 
hello printf 
hello fwrite 
hello printf 
hello fwrite

這是為什麼呢,這是跟C庫的緩衝資料有關,C庫緩衝資料分為三種(1)、無緩衝(2)、行緩衝(3)、全緩衝。

行緩衝就是往顯示器上寫,全緩衝就是往檔案裡寫。

在上面的現象中,write不受影響是因為它屬於系統呼叫,沒有緩衝區,而printf和fwrite會自帶緩衝區,當發生重定向到普通檔案的時候,它就會從行緩衝轉變為全緩衝,也就是會往檔案裡面寫寫,但是我們緩衝區裡的資料,即使fork也不會立即被重新整理,當程序退出後會統一重新整理,寫入檔案,但是fork的時候會發生寫時拷貝,也就是當父程序準備重新整理的時候,子程序就已經有了一份相同的資料,所以就會產生上面的現象。

瞭解下重定向。
重定向分為三種:
輸出重定向(>) 也就是關閉fd為1下標所指向的內容
輸入重定向(<) 同理就是關閉fd為0下標所指向的內容
追加重定向(>>) 後面多一個追加選項

stat函式

#include <sys/stat.h>
#include <unistd.h>
int stat(const char *file_name, struct stat *buf);

函式說明: 通過檔名filename獲取檔案資訊,並儲存在buf所指的結構體stat中
返回值: 執行成功則返回0,失敗返回-1,錯誤程式碼存於errno

錯誤程式碼:
ENOENT 引數file_name指定的檔案不存在
ENOTDIR 路徑中的目錄存在但卻非真正的目錄
ELOOP 欲開啟的檔案有過多符號連線問題,上限為16符號連線
EFAULT 引數buf為無效指標,指向無法存在的記憶體空間
EACCESS 存取檔案時被拒絕
ENOMEM 核心記憶體不足
ENAMETOOLONG 引數file_name的路徑名稱太長
eg:

#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    struct stat buf;
    stat("/etc/hosts", &buf);
    printf("/etc/hosts file size = %d/n", buf.st_size);
}
struct stat {
    dev_t         st_dev;       //檔案的裝置編號
    ino_t         st_ino;       //節點
    mode_t        st_mode;      //檔案的型別和存取的許可權
    nlink_t       st_nlink;     //連到該檔案的硬連線數目,剛建立的檔案值為1
    uid_t         st_uid;       //使用者ID
    gid_t         st_gid;       //組ID
    dev_t         st_rdev;      //(裝置型別)若此檔案為裝置檔案,則為其裝置編號
    off_t         st_size;      //檔案位元組數(檔案大小)
    unsigned long st_blksize;   //塊大小(檔案系統的I/O 緩衝區大小)
    unsigned long st_blocks;    //塊數
    time_t        st_atime;     //最後一次訪問時間
    time_t        st_mtime;     //最後一次修改時間
    time_t        st_ctime;     //最後一次改變時間(指屬性)
};

先前所描述的st_mode 則定義了下列數種情況:

 S_IFMT   0170000    檔案型別的位遮罩
    S_IFSOCK 0140000    scoket
    S_IFLNK 0120000     符號連線
    S_IFREG 0100000     一般檔案
    S_IFBLK 0060000     區塊裝置
    S_IFDIR 0040000     目錄
    S_IFCHR 0020000     字元裝置
    S_IFIFO 0010000     先進先出

    S_ISUID 04000     檔案的(set user-id on execution)位
    S_ISGID 02000     檔案的(set group-id on execution)位
    S_ISVTX 01000     檔案的sticky位

    S_IRUSR(S_IREAD) 00400     檔案所有者具可讀取許可權
    S_IWUSR(S_IWRITE)00200     檔案所有者具可寫入許可權
    S_IXUSR(S_IEXEC) 00100     檔案所有者具可執行許可權

    S_IRGRP 00040             使用者組具可讀取許可權
    S_IWGRP 00020             使用者組具可寫入許可權
    S_IXGRP 00010             使用者組具可執行許可權

    S_IROTH 00004             其他使用者具可讀取許可權
    S_IWOTH 00002             其他使用者具可寫入許可權
    S_IXOTH 00001             其他使用者具可執行許可權 

上述的檔案型別在POSIX中定義了檢查這些型別的巨集定義:

    S_ISLNK (st_mode)    判斷是否為符號連線
    S_ISREG (st_mode)    是否為一般檔案
    S_ISDIR (st_mode)    是否為目錄
    S_ISCHR (st_mode)    是否為字元裝置檔案
    S_ISBLK (s3e)        是否為先進先出
    S_ISSOCK (st_mode)   是否為socket