天天看點

資訊安全系統設計基礎第十周學習總結

系統級I/O

  • 輸入/輸出(I/O)是在主存和外部裝置之間拷貝資料的過程。
  • 輸入操作是從I/O裝置拷貝資料到主存。
    • I/O→主存
  • 輸出操作是從主存拷貝資料到I/O裝置。
    • 主存→I/O

Unix I/O

  • 所有的I/O裝置都被模型化為檔案。
  • 所有的輸入和輸出都被當作對相應檔案的讀和寫來執行。
  • 打開檔案
    • 應用程式通過要求核心打開相應的檔案,來宣告它想要通路一個I/O裝置。
    • 核心傳回一個小的非負整數,稱為描述符。
    • 核心記錄有關這個打開檔案的所有資訊。
    • 應用程式隻需要記住操作符即可。
  • 建立每個程序開始時三個打開的檔案
    • 标準輸入(描述符為0)|STDIN_FILENO
    • 标準輸出(描述符為1)|STDOUT_FILENO
    • 标準錯誤(描述符為2)|STDERR_FILENO
  • 改變目前的檔案位置
    • 對于打開的檔案,核心儲存着該檔案的位置k,初始值為0
    • k為從檔案開頭起始的位元組偏移量
    • 應用程式通過執行seek操作,顯式地設定檔案的目前位置為k
  • 讀寫檔案
    • 讀操作:
      • 從檔案拷貝n>0個位元組到存儲器
      • 從目前檔案位置k開始
      • 将k增加到k+n
      • 給定一個大小為m位元組的檔案,當k≥m時(即目前檔案位置已到檔案尾),此時執行讀操作會觸發end-of-file(EOF)條件。
      • 在檔案的結尾并未明确的EOF符号
    • 寫操作:
      • 從存儲器拷貝n>0個位元組到一個檔案
      • 更新k
  • 關閉檔案
    • 應用完成對檔案的通路後,會通知核心關閉該檔案。
    • 核心釋放檔案打開時建立的資料結構,并恢複描述池。
    • 無論一個程序因為何種原因終止,核心都會關閉所有打開的檔案并釋放它們的存儲器資源。

打開和關閉檔案

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

int open(char *filename,int flags,mode_t mode)
           
  • sys/types.h 基本系統資料類型,其中包括核心位址、系統時間、裝置号、檔案描述集、檔案位置等類型。
  • sys/stat.h 用于擷取一個檔案的所有資訊,包括檔案對應的模式、裝置号碼、檔案所有者、最後被通路的時間、最後被修改的時間等。ls -l指令就用到了該頭檔案中所包含的類型。
  • fcntl.h 檔案資訊控制類型(file control),定義了很多宏和open.fcntl函數原型。

open函數

  • char *filename 參數filename指向欲打開的檔案路徑字元串。
  • int flags 旗标
    • O_RDONLY 以隻讀方式打開檔案
      • define O_RDONLY 00

    • O_WRONLY 以隻寫方式打開檔案
      • define O_WRONLY 01

    • O_RDWR 以可讀寫方式打開檔案。
      • define O_RDWR 02

    • 上述三種旗标是互斥的,也就是不可同時使用,但可與下列的旗标利用OR(|)運算符組合。
    • O_CREAT 若欲打開的檔案不存在則自動建立該檔案。
      • define O_CREAT 00000100

    • O_TRUNC 若檔案存在并且以可寫的方式打開時,此旗标會令檔案長度清為0,而原來存于該檔案的資料也會消失。
      • define O_TRUNC 00001000

    • O_APPEND 當讀寫檔案時會從檔案尾開始移動,也就是所寫入的資料會以附加的方式加入到檔案後面。
      • define O_APPEND 00002000

    • mode_t mode 檔案權限标志
      1. S_IRUSR 所有者擁有讀權限
        2. S_IWUSR 所有者擁有寫權限
        3. S_IXUSR 所有者擁有執行權限
        4. S_IRGRP 群組擁有讀權限
        5. S_IWGRP 群組擁有寫權限
        6. S_IXGRP 群組擁有執行權限
        7. S_IROTH 其他使用者擁有讀權限
        8. S_IWOTH 其他使用者擁有寫權限
        9. S_IXOTH 其他使用者擁有執行權限
                 
    • 檔案權限标志也可以用權重數字表示,這組數字被成為umask變量,它的類型是mode_t,是一個無符号八進制數。
      • umask變量由3位數字組成,數字的每一位代表一類權限,使用者所獲得的權限是權重數值的總和。
      • 例如764表示所有者擁有讀、寫和執行權限,群組擁有讀和寫權限,其他使用者擁有讀權限。
      • 0表示沒有任何權限。
      • 每個程式都有一個umask的值,并且預設為022。可以通過umask()函數調用設定。
        • umask()會将系統umask值設成參數mask&0777後的值,然後将先前的umask值傳回。在使用open()建立新檔案時,該參數 mode并非真正建立檔案的權限,而是(mode& ~umask)的權限值。
        • 例如,在建立檔案時指定檔案權限為0666,通常umask值預設為 022,則該檔案的真正權限則為0666&~022=0644
    • 書上例子解析:
      #define DEF_MODE S_IRUSER|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH //擁用者、群組、其他人都擁有讀寫權限,相當于666
        #define DEF_MASK S_IWGRP|S_IWOTH //使用者組、其他人擁有寫權限,相當于022
        umask(DEF_UMASK); //将目前的umask值設定成022
        fd = open("foo.txt",O_CREAT|O_TRUNC|O_WRONLY,DEF_MODE); //DEF_MODE此時的值應該為DEF_MODE &~DEF_UMASK,即666&~022=644,則檔案的擁有者有讀寫權利,所有其他的使用者隻有讀權利。
                 
    • 傳回值 若成功則為新檔案描述符,若出錯為-1。傳回的描述符總是在程序中目前沒有打開的最小描述符。
    • 示例:fd = open("foo.txt",O_WRONLY|O_APPEND,0)
      • 表示打開一個已存在的檔案,并在後面添加一些資料。

close函數

#include <unistd.h>

int close(int fd);
           
  • unistd.h 提供了close()函數。
  • 傳回值:成功傳回0,出錯傳回-1并設定errno
  • 參數fd是要關閉的檔案描述符。
    • 需要說明的是,當一個程序終止時,核心對該程序所有尚未關閉的檔案描述符調用close關閉,是以即使使用者程式不調用close,在終止時核心也會自動關閉它打開的所有檔案。
  • 若省略fd,則将關閉Open語句打開的所有活動檔案。

讀和寫檔案

讀檔案

#include <unist.h>
ssize_t read(int fd,void *buf,size_t n);
           
  • fd 檔案描述符
  • *buf 緩沖區指針,用于儲存讀出來的資料。
  • n 所需要讀取的位元組數
  • 傳回值類型為ssize_t 表示有符号的size_t。
    • size_t(size type),表示一種整型類型,包含int、long等。
    • 傳回所讀取的位元組數;0(讀到EOF);-1(出錯)。
    • 以下幾種情況會導緻讀取到的位元組數小于 n :
      • A. 讀取普通檔案時,讀到檔案末尾還不夠 n 位元組。例如:如果檔案隻有 30 位元組,而我們想讀取 100 位元組,那麼實際讀到的隻有 30 位元組,read 函數傳回 30 。此時再使用 read 函數作用于這個檔案會導緻 read 傳回 0 。
      • B. 從終端裝置(terminal device)讀取時,一般情況下每次隻能讀取一行。
      • C. 從網絡讀取時,網絡緩存可能導緻讀取的位元組數小于 n位元組。
      • D. 讀取 pipe 或者 FIFO 時,pipe 或 FIFO 裡的位元組數可能小于 n 。
      • E. 從面向記錄(record-oriented)的裝置讀取時,某些面向記錄的裝置(如錄音帶)每次最多隻能傳回一個記錄。
      • F. 在讀取了部分資料時被信号中斷。

寫檔案

#include <unist.h>
sszize_t write(int fd,const void *buf,size_t n);
           
  • const void *buf 資料來源buf,const所描述的對象具有不變性,不可更新。
  • n 從存儲器位置buf拷貝至多n個位元組到描述符fd的目前檔案位置。
  • 傳回值 一般等于n,否則就是出錯。
    • 常見出錯的原因:
      • 磁盤空間滿了
      • 超過檔案大小限制

書上cpstdin.c代碼測試

書上的源代碼僅為理論上講述的程式,說明了一次一個位元組地從标準輸入拷貝到标準輸出的功能,但是并未有明顯的表示。是以我将代碼修改了,通過兩個檔案foo.txt與foo1.txt來展現該功能。

  • foo.txt文檔中存放着123456的文本
  • foo1.txt為空文檔。
  • 代碼功能将foo.txt中的文本一次一個地寫入foo1.txt文檔中。
  • 代碼如下:
    /* $begin cpstdin */	
      #include "csapp.h"
      #include <sys/types.h>
      #include <sys/stat.h>
      #include <fcntl.h>
      int main(void)
      {
         char c = 1;
         int fd1,fd2;
         fd1 = open("foo.txt",O_RDWR);//打開foo.txt,并将檔案描述符儲存至fd1
         fd2 = open("foo1.txt",O_RDWR);//打開	foo1.txt,并将檔案描述符儲存至fd2
         while(read(fd1,&c, 1) != 0)//當未讀到EOF,則循環進行
         write(fd2, &c, 1);//将一個字元寫入foo1.txt中
        exit(0);
      }
      
      /* $end cpstdin */
               
  • 運作結果如下:

用RIO包健壯地讀寫

  • RIO(Robust I/O,健壯地I/O)包:它會自動為你處理不足值。
    • 不足值:read和write傳送的位元組比應用程式要求地要少。
  • RIO提供了兩類不同的函數:
    • 無緩沖的輸入輸出函數。 沒有應用級緩沖,它們對将二進制資料讀寫到網絡和從網絡讀寫二進制資料尤其有用。
    • 帶緩沖的輸入函數。 高效地從檔案中讀取檔案行和二進制資料,這些檔案的内容緩存在應用級緩存區内。

RIO的無緩沖的輸入輸出函數

  • 通過調用rio_readn和rio_writen函數,應用程式可以在存儲器和檔案之間直接傳送資料。
    #include "csapp.h"
      ssize_t rio_readn(int fd,void *usrbuf,size_t n);
               
  • void *usrbuf 存儲器位置
  • size_t n 傳送的位元組數
  • 傳回值 若成功則為傳送的位元組數,若EOF則為0,若出錯則為-1
  • 功能 從描述符fd的目前檔案位置最多傳送n個位元組到存儲器位置usrbuf。
    #include "csapp.h"
      ssize_t rio_writen(int fd,viod *usrbuf,size_t n);
               
  • 傳回值 若成功則為傳送的位元組數,若出錯則為-1
  • 功能 從位置usrbuf傳送n個位元組道描述符fd。

RIO帶緩沖地輸入函數

  • 一個文本行就是一個由換行符結尾的ASCII碼字元序列。
  • 在Unix系統中,換行符的數值未0x0a。
    #include "csapp.h"
      void rio_readinitb(rio_t *rp,int fd);
               
  • 每打開一個描述符都會調用一次rio_readinitb,它将描述符fd和位址rp處的一個類型為rio_t的讀緩沖區練習起來。
ssize_t rio_readlineb(rio_t *rp,void *usrbuf,size_t maxlen);
           
  • rio_t 設定一個位址
  • void *usrbuf 緩存區位址
  • size_t maxlen 讀取的最大長度
  • 功能 從一個内部讀緩沖區拷貝一個文本行,當緩沖區變空時,會自動地調用read重新填滿緩沖區。
  • rio_readlineb函數最多讀maxlen-1個位元組,餘下的一個字元留給結尾的空字元。
  • 超過maxlen-1位元組的文本行被截斷,并用一個空字元結束。
  • ssize_t rio_readnb(rio_t *rp,void *usrbuf,size_t n);
  • 功能 對于既包含文本行也包含二進制資料的文本,進行拷貝文本行操作。
  • 從檔案rp最多讀n個位元組道存儲器位置usrbuf。

對同一描述符,對rio_readlineb和rio_readnb的調用可以任意交叉進行,但不應該和無緩沖的rio_readn函數交叉使用。

讀取檔案中繼資料

  • 應用程式能夠通過調用stat和fstat函數,檢索到關于檔案的資訊(也稱為檔案的中繼資料)。
    #include <unistd.h>
      #include <sys/stat.h>
      int stat(const char *filename,struct stat *buf);
               
  • filename 檔案名
  • struct stat *buf 結構體stat中各個成員,如下:
    struct stat {
    
      mode_t     st_mode;       //檔案對應的模式,檔案,目錄等
    
      ino_t      st_ino;       //inode節點号
    
      dev_t      st_dev;        //裝置号碼
    
      dev_t      st_rdev;       //特殊裝置号碼
    
      nlink_t    st_nlink;      //檔案的連接配接數
    
      uid_t      st_uid;        //檔案所有者
    
      gid_t      st_gid;        //檔案所有者對應的組
    
      off_t      st_size;       //普通檔案,對應的檔案位元組數
    
      time_t     st_atime;      //檔案最後被通路的時間
    
      time_t     st_mtime;      //檔案内容最後被修改的時間
    
      time_t     st_ctime;      //檔案狀态改變時間
    
      blksize_t st_blksize;    //檔案内容對應的塊大小
    
      blkcnt_t   st_blocks;     //偉建内容對應的塊數量
    
    };
    
    
      int fstat(int fd,struct stat *buf);
               
  • int fd 檔案描述符
  • 不同的檔案類型
    • 普通檔案
    • 目錄檔案
    • 網絡套接字
  • 宏指令根據st_mode成員來确定檔案的類型
    • S_ISREG() —— 這是一個普通檔案嗎?
    • S_ISDIR() —— 這是一個目錄檔案嗎?
    • S_ISSOCK() —— 這是一個網絡套接字嗎?

共享檔案

  • 核心用三個資料結構來表示打開的檔案:
    • 描述符表
      • 每個程序都有它獨立的描述符表
    • 檔案表
      • 打開檔案的集合是由一張檔案表來表示的,所有的程序共享這張表。
      • 每個檔案表的表項組成:
        • 目前的檔案位置
        • 引用計數
        • 指向v-node表中對應表項的指針
    • v-node表
      • 所有的程序共享這張v-node表。

I/O重定向

  • 利用dup2函數進行I/O重定向工作
    #include <unistd.h>
      	int dup2(int oldfd,int newfd);
               
  • dup2函數拷貝描述符表表項oldfd到描述符表表項newfd,覆寫描述符表表項newfd以前的内容。
  • 如果newfd已經打開了,dup2會在拷貝oldfd之前關閉newfd。

标準I/O

  • ANSI C定義了一組進階輸入輸出函數,成為标準I/O庫。
    • 提供了打開和關閉檔案的函數(fopen/fclose)
    • 讀和寫位元組的函數(fread/fwrite)
    • 讀和寫字元串的函數(fgets/fputs)
    • 複雜的格式化的I/O函數(scanf/printf)
  • 标準I/O庫将一個打開的檔案模型化為一個流。
  • 每個ANSI C程式開始時都有三個打開的流stdin、stdout和stderr。
  • 類型為FILE的流是對檔案描述符和流緩沖區的抽象。

實驗代碼

  1. cp1.c

    該代碼的功能為生成一個與其一樣内容的檔案。

    運作測試(拷貝cp1.c至cp2.c)

    2.echostate.c

    該代碼的功能為檢視此時echo值為多少,配合setecho使用。

    3.setecho.c

    該代碼的功能是輸入y時,即可顯示輸入。輸入除y外的内容,即可隐藏輸入

    運作結果:

    4.fileinfo.c

    該代碼的功能是顯示檔案的資訊

    5.filesize.c

    該代碼的功能是顯示檔案的位元組數

    6.who1.c

    該代碼的功能是将系統使用者操作顯示出來。

    7.ls1.c

    該代碼的功能是顯示目前目錄的檔案

    8.ls2.c

    該代碼的功能同ls1.c相同,但多了檔案的權限資訊。

    9.spwd.c

    該代碼的功能是顯示目前檔案夾路徑,同pwd。

    10.testioctl.c

    該代碼的功能是顯示檔案内容的行列。

參考資料

1.《Computer.Systems.A.Programmer's.Perspective.2nd.CN》教材

2.《打開檔案、建立檔案和關閉檔案操作》http://book.51cto.com/art/200912/169537.htm

3.《linux下的檔案操作函數》http://jingyan.baidu.com/article/6dad5075c33056a123e36ecf.html

4.《linux下的umask()函數》http://www.360doc.com/content/12/0605/16/9305922_216186419.shtml