国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

當前位置: 首頁 > news >正文

揚州城鄉(xiāng)建設局網站移動端優(yōu)化

揚州城鄉(xiāng)建設局網站,移動端優(yōu)化,wordpress更新文章同步微信,海外網站seo參考引用 UNIX 環(huán)境高級編程 (第3版)嵌入式Linux C應用編程-正點原子 1. 標準 I/O 庫簡介 標準 I/O 庫是指:標準 C 庫中用于文件 I/O 操作(如:讀、寫文件等)相關的一系列庫函數的集合 標準 I/O 庫函數相關的函數定義都在頭文件 &…

參考引用

  • UNIX 環(huán)境高級編程 (第3版)
  • 嵌入式Linux C應用編程-正點原子

1. 標準 I/O 庫簡介

  • 標準 I/O 庫是指:標準 C 庫中用于文件 I/O 操作(如:讀、寫文件等)相關的一系列庫函數的集合
    • 標準 I/O 庫函數相關的函數定義都在頭文件 <stdio.h> 中
    • 標準 I/O 庫函數構建于文件 I/O(open()、read()、write()、lseek()、close()等)這些系統調用之上,如:標準 I/O 庫函數 fopen() 就利用系統調用 open() 來執(zhí)行打開文件的操作

    為什么需要標準 I/O 庫?直接使用文件 I/O 系統調用不是更好嗎?

    • 設計庫函數是為了提供比底層系統調用更為方便、好用的調用接口,雖然標準 I/O 構建于文件 I/O 之上,但標準 I/O 卻有它自己的優(yōu)勢

  • 標準 I/O 和文件 I/O 的區(qū)別如下
    • 雖然標準 I/O 和文件 I/O 都是 C 語言函數,但是標準 I/O 是標準 C 庫函數,而文件 I/O 則是 Linux 系統調用
    • 標準 I/O 是由文件 I/O 封裝而來,標準 I/O 內部實際上是調用文件 I/O 來完成實際操作的
    • 可移植性:標準 I/O 相比于文件 I/O 具有更好的可移植性
      • 通常對于不同的操作系統,其內核向應用層提供的系統調用往往都是不同,如:系統調用的定義、功能、參數列表、返回值等往往都不同
      • 而對于標準 I/O 來說,由于很多操作系統都實現了標準 I/O 庫,標準 I/O 庫在不同的操作系統之間其接口定義幾乎是一樣的,所以標準 I/O 在不同操作系統之間相比于文件 I/O 具有更好的可移植性
    • 性能、效率:標準 I/O 庫在用戶空間維護了自己的 stdio 緩沖區(qū),所以標準 I/O 是帶有緩存的,而文件 I/O 在用戶空間是不帶有緩存的,所以在性能、效率上,標準 I/O 要優(yōu)于文件 I/O

2. FILE 指針

  • 所有文件 I/O 函數(open()、read()、write()、lseek()等)都是圍繞文件描述符進行的,當調用 open()函數打開一個文件時,即返回一個文件描述符 fd,然后該文件描述符就用于后續(xù)的 I/O 操作
  • 而對于標準 I/O 庫函數來說,它們的操作是圍繞 FILE 指針進行的,當使用標準 I/O 庫函數打開或創(chuàng)建一個文件時,會返回一個指向 FILE 類型對象的指針(FILE *),使用該 FILE 指針與被打開或創(chuàng)建的文件相關聯,然后該 FILE 指針就用于后續(xù)的標準 I/O 操作
  • FILE 指針的作用相當于文件描述符,FILE 指針用于標準 I/O 庫函數中,而文件描述符則用于文件 I/O 系統調用中
  • FILE 是一個結構體數據類型,它包含了標準 I/O 庫函數為管理文件所需要的所有信息,包括用于實際 I/O 的文件描述符、指向文件緩沖區(qū)的指針、緩沖區(qū)的長度、當前緩沖區(qū)中的字節(jié)數以及出錯標志等
    • FILE 數據結構定義在標準 I/O 庫函數頭文件 stdio.h 中

3. 標準輸入、標準輸出和標準錯誤

  • 用戶通過標準輸入設備與系統進行交互,進程將從標準輸入 (stdin) 文件中得到輸入數據,將正常輸出數據(如:printf 打印輸出)輸出到標準輸出(stdout) 文件,而將錯誤信息(如:函數調用報錯)輸出到標準錯誤 (stderr) 文件
    • 標準輸出文件和標準錯誤文件都對應終端的顯示器,而標準輸入文件則對應于鍵盤
  • 每個進程啟動之后都會默認打開標準輸入、標準輸出以及標準錯誤,得到三個文件描述符,即 0(標準輸入)、1(標準輸出)、2(標準錯誤),在應用編程中可以使用宏 STDIN_FILENO、STDOUT_FILENO 和 STDERR_FILENO 分別代表 0、1、2,這些宏定義在 unistd.h 頭文件中
    #define STDIN_FILENO 0 /* Standard input. */
    #define STDOUT_FILENO1 /* Standard output. */
    #define STDERR_FILENO2 /* Standard error output. */
    
  • 0、1、2 是文件描述符,只能用于文件 I/O,標準 I/O 中,需圍繞 FILE 類型指針進行,在 stdio.h 頭文件定義
    // struct _IO_FILE 結構體就是 FILE 結構體,使用 typedef 進行了重命名
    extern struct _IO_FILE *stdin; /* Standard input stream. */
    extern struct _IO_FILE *stdout; /* Standard output stream. */
    extern struct _IO_FILE *stderr; /* Standard error output stream. *//* C89/C99 say they're macros. */
    #define stdin stdin
    #define stdout stdout
    #define stderr stderr
    

4. 打開文件 fopen()、關閉文件 fclose()

  • 標準 I/O 中,將使用庫函數 fopen() 打開或創(chuàng)建文件
    #include <stdio.h>// path:參數 path 指向文件路徑,可以是絕對路徑或相對路徑
    // mode:參數 mode 指定了對該文件的讀寫權限,是一個字符串
    // 返回值:調用成功返回一個指向 FILE 類型對象的指針(FILE *)
    FILE *fopen(const char *path, const char *mode);
    
  • 參數 mode 字符串類型,可取值如下
    在這里插入圖片描述

在這里插入圖片描述

  • 雖然調用 fopen()函數新建文件時無法手動指定文件的權限,但卻有一個默認值
    S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH (0666)
    
  • 調用 fclose() 庫函數可以關閉一個由 fopen() 打開的文件
    #include <stdio.h>// 參數 stream 為 FILE 類型指針,調用成功返回 0;失敗將返回 EOF(也就是-1)
    int fclose(FILE *stream);
    

5. 讀文件和寫文件

  • 可以使用 fread() 和 fwrite() 庫函數對文件進行讀、寫操作
    #include <stdio.h>/*  ptr:fread() 將讀取到的數據存放在參數 ptr 指向的緩沖區(qū)中size:fread() 從文件讀取 nmemb 個數據項,每一個數據項大小為 size 個字節(jié),所以總共讀取 nmemb * size 個字節(jié)數據nmemb:參數 nmemb 指定了讀取數據項的個數stream:FILE 指針
    */
    size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
    /*  ptr:將參數 ptr 指向的緩沖區(qū)中的數據寫入到文件中size:參數 size 指定了每個數據項的字節(jié)大小,與 fread() 函數的 size 參數意義相同nmemb:參數 nmemb 指定了寫入的數據項個數,與 fread() 函數的 nmemb 參數意義相同stream:FILE 指針
    */
    size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
    

5.1 使用 fwrite() 將數據寫入到文件中

#include <stdio.h>
#include <stdlib.h>int main(void) {char buff[] = "Hello World!\n";FILE *fp = NULL;if ((fp = fopen("./test_file", "w")) == NULL) {perror("fopen error");exit(-1);}   printf("open success\n");if (fwrite(buff, 1, sizeof(buff), fp) < sizeof(buff)) {printf("fwrite error\n");fclose(fp);exit(-1);}   printf("write success\n");fclose(fp);exit(0);
}
$ gcc fw.c -o fw
$ ./fw 
open success
write success$ cat test_file 
Hello World!

5.2 使用 fread() 從文件中讀取數據

#include <stdio.h>
#include <stdlib.h>int main(void) {char buf[50] = {0};FILE *fp = NULL;int size;/* 只讀方式打開文件 */if ((fp = fopen("./test_file", "r")) == NULL) {perror("fopen error");exit(-1);}   printf("文件打開成功!\n");/* 讀取 12 * 1=12 個字節(jié)的數據 */if ((size = fread(buf, 1, 12, fp)) < 12) {if (ferror(fp)) {  //使用 ferror 判斷是否是發(fā)生錯誤printf("fread error\n");fclose(fp);exit(-1);}   /* 如果未發(fā)生錯誤則意味著已經到達了文件末尾 */}   printf("成功讀取%d 個字節(jié)數據: %s\n", size, buf);/* 關閉文件 */fclose(fp);exit(0);
}
$ gcc fr.c -o fr
$ ./fr
文件打開成功!
成功讀取12 個字節(jié)數據: Hello World!$ cat test_file 
Hello World!

6. fseek 定位

  • 庫函數 fseek() 的作用類似系統調用 lseek(),用于設置文件讀寫位置偏移量
    • 調用庫函數 fread()、fwrite() 讀寫文件時,文件的讀寫位置偏移量會自動遞增,使用 fseek() 可手動設置文件當前的讀寫位置偏移量
    #include <stdio.h>// stream:FILE 指針
    // offset:與 lseek() 函數的 offset 參數意義相同
    // whence:與 lseek() 函數的 whence 參數意義相同
    int fseek(FILE *stream, long offset, int whence);
    

6.1 使用 fseek() 調整文件讀寫位置

#include <stdio.h>
#include <stdlib.h>int main(void) {FILE *fp = NULL;char rd_buf[100] = {0};char wr_buf[] = "www.baidu.com\n";int ret;/* 打開文件 */if ((fp = fopen("./test_file", "w+")) == NULL) {perror("fopen error");exit(-1);}   printf("open seccess\n");/* 寫文件 */if (fwrite(wr_buf, 1, sizeof(wr_buf), fp) < sizeof(wr_buf)) {printf("fwrite error\n");fclose(fp);exit(-1);}   printf("write success\n");/* 將讀寫位置移動到文件頭部 */if (fseek(fp, 0, SEEK_SET) < 0) {perror("fseek error\n");fclose(fp);exit(-1);}   /* 讀文件 */if ((ret = fread(rd_buf, 1, sizeof(wr_buf), fp)) < sizeof(wr_buf)) {printf("fread error\n");fclose(fp);exit(-1);}   printf("成功讀取 %d 個字節(jié)數據: %s\n", ret, rd_buf);/* 關閉文件 */fclose(fp);exit(0);
}
$ gcc fseek.c -o fseek
$ ./fseek 
open seccess
write success
成功讀取 14 個字節(jié)數據: www.baidu.com

7. ftell() 函數

  • 庫函數 ftell() 可用于獲取文件當前的讀寫位置偏移量
    #include <stdio.h>// 參數 stream 指向對應的文件,函數調用成功將返回當前讀寫位置偏移量;調用失敗將返回 -1
    long ftell(FILE *stream);
    

7.1 使用 fseek() 和 ftell() 獲取文件大小

#include <stdio.h>
#include <stdlib.h>int main(void) {FILE *fp = NULL;int ret;/* 打開文件 */if ((fp = fopen("./testApp.c", "r")) == NULL) {perror("fopen error");exit(-1);}printf("open success\n");/* 將讀寫位置移動到文件末尾 */if (fseek(fp, 0, SEEK_END) < 0>) {perror("fseek error");fclose(fp);exit(-1);}/* 獲取當前位置偏移量,也就得到了 testApp.c 整個文件的大小 */if ((ret = ftell(fp)) < 0) {perror("ftell error");fclose(fp);exit(-1);}   printf("file size: %d\n", ret);/* 關閉文件 */fclose(fp);exit(0);
}
$ gcc ftell.c -o ftell
$ ./ftell 
open success
file size: 13

8. 檢查或復位狀態(tài)

8.1 feof() 函數

  • 庫函數 feof() 用于測試參數 stream 所指文件的 end-of-file 標志
    • 如果 end-of-file 標志被設置了,則調用 feof() 函數將返回一個非零值
    • 如果 end-of-file 標志沒有被設置,則返回 0
    #include <stdio.h>int feof(FILE *stream);
    
    // 當文件的讀寫位置移動到了文件末尾時,end-of-file 標志將會被設置
    if (feof(file)) {/* 到達文件末尾 */
    }
    else {/* 未到達文件末尾 */
    }
    

8.2 ferror() 函數

  • 庫函數 ferror() 用于測試參數 stream 所指文件的錯誤標志
    • 如果錯誤標志被設置了,則調用 ferror() 函數將返回一個非零值
    • 如果錯誤標志沒有被設置,則返回 0
    #include <stdio.h>int ferror(FILE *stream);
    
    // 當對文件的 I/O 操作發(fā)生錯誤時,錯誤標志將會被設置
    if (ferror(file)) {/* 發(fā)生錯誤 */
    }
    else {/* 未發(fā)生錯誤 */
    }
    

8.3 clearerr() 函數

  • 庫函數 clearerr() 用于清除 end-of-file 標志和錯誤標志
    • 當調用 feof() 或 ferror() 校驗這些標志后,通常需要清除這些標志,避免下次校驗時使用到的是上一次設置的值,此時可以手動調用 clearerr() 函數清除標志
    • 對于 end-of-file 標志,除使用 clearerr() 顯式清除外,當調用 fseek() 成功時也會清除文件的 end-of-file 標志
    #include <stdio.h>void clearerr(FILE *stream);
    
  • 示例
#include <stdio.h>
#include <stdlib.h>int main(void) {FILE *fp = NULL;char buf[20] = {0};/* 打開文件 */if ((fp = fopen("./testApp.c", "r")) == NULL) {perror("fopen error");exit(-1);}printf("文件打開成功!\n");/* 將讀寫位置移動到文件末尾 */if (fseek(fp, 0, SEEK_END) < 0) {perror("fseek error");fclose(fp);exit(-1);}/* 讀文件 */if (fread(buf, 1, 10, fp) < 10) {if (feof(fp))printf("end-of-file 標志被設置, 已到文件末尾!\n");clearerr(fp);  // 清除標志}/* 關閉文件 */fclose(fp);exit(0);
}

9. 格式化 I/O

9.1 格式化輸出

  • C 庫函數提供了 5 個格式化輸出函數,包括:printf()、fprintf()、dprintf()、sprintf()、snprintf()
    #include <stdio.h>int printf(const char *format, ...);int fprintf(FILE *stream, const char *format, ...);
    int dprintf(int fd, const char *format, ...);int sprintf(char *buf, const char *format, ...);
    int snprintf(char *buf, size_t size, const char *format, ...);
    
  • 這 5 個函數都是可變參函數,它們都有一個共同的參數 format,這是一個字符串,稱為格式控制字符串,用于指定后續(xù)的參數如何進行格式轉換,所以才把這些函數稱為格式化輸出
    • printf() 函數用于將格式化數據寫入到標準輸出
    printf("Hello World!\n");
    printf("%d\n", 5);
    
    • dprintf() 和 fprintf() 函數用于將格式化數據寫入到指定的文件中,兩者不同之處在于,fprintf() 使用 FILE 指針指定對應的文件,而 dprintf()則使用文件描述符 fd 指定對應的文件
    fprintf(stderr, "Hello World!\n");
    fprintf(stderr, "%d\n", 5);dprintf(STDERR_FILENO, "Hello World!\n");
    dprintf(STDERR_FILENO, "%d\n", 5);
    
    • sprintf()、snprintf() 函數可將格式化的數據存儲在用戶指定的緩沖區(qū) buf 中
    char buf[100];
    sprintf(buf, "Hello World!\n");// 一般會使用這個函數進行格式化轉換,并將轉換后的字符串存放在緩沖區(qū)中
    // 如:將數字 100 轉換為字符串 "100",將轉換后得到的字符串存放在 buf 中
    // sprintf() 函數會在字符串尾端自動加上一個字符串終止字符 '\0'
    char buf[20] = {0};
    sprintf(buf, "%d", 100);
    

    sprintf() 函數可能會造成由參數 buf 指定的緩沖區(qū)溢出,因為緩沖區(qū)溢出會造成程序不穩(wěn)定甚至安全隱患,為解決這個問題,引入 snprintf() 函數,在該函數中,使用參數 size 顯式的指定緩沖區(qū)的大小

    • 如果寫入到緩沖區(qū)的字節(jié)數大于參數 size 指定的大小,超出的部分將會被丟棄
    • 如果緩沖區(qū)空間足夠大,snprintf() 函數就會返回寫入到緩沖區(qū)的字符數

9.2 格式控制字符串 format:輸出

  • 格式控制字符串由兩部分組成:普通字符(非 % 字符)和轉換說明
    • 普通字符會進行原樣輸出,每個轉換說明都會對應后續(xù)的一個參數,通常有幾個轉換說明就需要提供幾個參數(除固定參數之外的參數),使之一一對應,用于控制對應的參數如何進行轉換
    printf("轉換說明 1 轉換說明 2 轉換說明 3", arg1, arg2, arg3);
    
  • 每個轉換說明都是以 % 字符開頭,其格式如下所示(使用 [] 括起來的部分是可選的)
    /*flags:標志,用于規(guī)定輸出樣式,可包含 0 個或多個標志width:輸出最小寬度,表示轉換后輸出字符串的最小寬度precision:精度,前面有一個點號 "."length:長度修飾符type:轉換類型,指定待轉換數據的類型
    */
    %[flags][width][.precision][length]type
    
9.2.1 type 類型
  • type 用于指定輸出數據的類型,type 字段使用一個字符(字母字符)來表示
    在這里插入圖片描述

在這里插入圖片描述

9.2.2 flags 樣式標志
  • flags 規(guī)定輸出樣式,% 后面可以跟 0 個或多個以下標志
    在這里插入圖片描述

在這里插入圖片描述

9.2.3 width 輸出寬度
  • 最小的輸出寬度,用十進制數來表示輸出的最小位數
    • 若實際的輸出位數大于指定的輸出的最小位數,則以實際的位數進行輸出
    • 若實際的位數小于指定輸出的最小位數,則可按照指定的 flags 標志補 0 或補空格
      在這里插入圖片描述
9.2.4 precision 精度
  • 精度字段以點號 “.” 開頭,后跟一個十進制正數,可取值如下
    在這里插入圖片描述

在這里插入圖片描述

9.2.5 length 長度修飾符
  • 長度修飾符指明待轉換數據的長度,因為 type 字段指定的的類型只有 int、unsigned int 以及 double 等幾種數據類型,但是 C 語言內置的數據類型不止這幾種,如:16bit 的 short、unsigned short,8bit 的 char、unsigned char,64bit 的 long long 等,為了能夠區(qū)別不同長度的數據類型,于是長度修飾符(length)應運而生
  • length 長度修飾符也是使用字符(字母字符)來表示,結合 type 字段以確定不同長度的數據類型
    printf("%hd\n", 12345);   // 將數據以 short int 類型進行轉換
    printf("%ld\n", 12345);   // 將數據以 long int 類型進行轉換
    printf("%lld\n", 12345);  // 將數據以 long long int 類型進行轉換
    

在這里插入圖片描述

9.3 格式化輸入

  • C 庫函數提供了 3 個格式化輸入函數,包括:scanf()、fscanf()、sscanf()
    #include <stdio.h>int scanf(const char *format, ...);
    int fscanf(FILE *stream, const char *format, ...);
    int sscanf(const char *str, const char *format, ...);
    
    • 這 3 個格式化輸入函數也是可變參函數,它們都有一個共同的參數 format,同樣也稱為格式控制字符串,用于指定輸入數據如何進行格式轉換
    • scanf() 函數可將用戶輸入(標準輸入)的數據進行格式化轉換
      • 當程序中調用 scanf() 的時候,終端會被阻塞,等待用戶輸入數據,此時可以通過鍵盤輸入一些字符,如:數字、字母或者其它字符,輸入完成按回車即可
    int a, b, c;
    scanf("%d %d %d", &a, &b, &c);
    
    • fscanf() 函數從 FILE 指針指定文件中讀取數據,并將數據進行格式化轉換
      • 標準輸入文件的數據就是用戶輸入的數據,如:通過鍵盤輸入的數據
    int a, b, c;
    fscanf(stdin, "%d %d %d", &a, &b, &c);
    
    • sscanf() 函數從參數 str 所指向的字符串中讀取數據,并將數據進行格式化轉換
    char *str = "5454 hello";
    char buf[10];
    int a;sscanf(str, "%d %s", &a, buf);
    

9.4 格式控制字符串 format:輸入

  • format 字符串包含一個或多個轉換說明,每一個轉換說明都是以百分號 “%”,轉換說明格式如下
    /*width:最大字符寬度length:長度修飾符,與格式化輸出函數的 format 相同type:指定輸入數據的類型
    */
    %[*][width][length]type
    %[m][width][length]type
    
  • % 后面可選擇性添加星號 * 或字母 m
    • 如果添加了星號*,格式化輸入函數會按照轉換說明的指示讀取輸入,但是丟棄輸入,意味著不需要對轉換后的結果進行存儲,所以也就不需要提供相應的指針參數
    • 如果添加了 m,它只能與 %s、%c 以及 %[ 一起使用,調用者無需分配相應的緩沖區(qū)來保存格式轉換后的數據,原因在于添加了 m,這些格式化輸入函數內部會自動分配足夠大小的緩沖區(qū),并將緩沖區(qū)的地址值通過與該格式轉換相對應的指針參數返回出來,該指針參數應該是指向 char* 變量的指針。隨后,當不再需要此緩沖區(qū)時,調用者應調用 free() 函數來釋放此緩沖區(qū)
    char *buf;scanf("%ms", &buf);
    ......free(buf);
    
9.4.1 type 類型
  • 此 type 字段與格式化輸出函數中的 format 參數的 type 字段是同樣的意義,用于指定輸入數據的類型
    在這里插入圖片描述

在這里插入圖片描述

9.4.2 width 最大字符寬度
  • 是一個十進制表示的整數,用于指定最大字符寬度,當達到此最大值或發(fā)現不匹配的字符時(以先發(fā)生者為準),字符的讀取將停止。大多數 type 類型會丟棄初始的空白字符,并且這些丟棄的字符不會計入最大字符寬度。對于字符串轉換來說,scanf() 會在字符串末尾自動添加終止符 “\0”,最大字符寬度中不包括此終止符
    scanf("%4s", buf);  // 匹配字符串,字符串長度不超過 4 個字符
    // 用戶輸入 abcdefg,按回車,那么只能將 adcd 作為一個字符串存儲在 buf 數組中
    
9.4.3 length 長度修飾符
  • 與格式化輸出函數的格式控制字符串 format 中的 length 字段意義相同,用于對 type 字段進行修飾,擴展識別更多不同長度的數據類型
    scanf("%hd", var);   // 匹配 short int 類型數據
    scanf("%hhd", var);  // 匹配 signed char 類型數據
    scanf("%ld", var);   // 匹配 long int 類型數據
    scanf("%f", var);    // 匹配 float 類型數據
    scanf("%lf", var);   // 匹配 double 類型數據
    scanf("%Lf", var);   // 匹配 long double 類型數據
    

在這里插入圖片描述

10. I/O 緩沖

  • 1、首先,應用程序調用標準 I/O 庫函數將用戶數據寫入到 stdio 緩沖區(qū)中,stdio 緩沖區(qū)是由 stdio 庫所維護的用戶空間緩沖區(qū)
  • 2、然后,針對不同的緩沖模式,當滿足條件時,stdio 庫會調用文件 I/O(系統調用 I/O)將 stdio 緩沖區(qū)中緩存的數據寫入到內核緩沖區(qū)中,內核緩沖區(qū)位于內核空間
  • 3、最終,由內核向磁盤設備發(fā)起讀寫操作,將內核緩沖區(qū)中的數據寫入到磁盤(或者從磁盤設備讀取數據到內核緩沖區(qū))
    在這里插入圖片描述

10.1 文件 I/O 的內核緩沖

  • read() 和 write() 系統調用在進行文件讀寫操作時并不會直接訪問磁盤設備,而是僅僅在用戶空間緩沖區(qū)和內核緩沖區(qū)之間復制數據。如:調用 write() 函數將 5 個字節(jié)數據從用戶空間內存拷貝到內核空間的緩沖區(qū)中
    write(fd, "Hello", 5);  // 寫入 5 個字節(jié)數據
    
  • 調用 write() 后僅僅只是將這 5 個字節(jié)數據拷貝到了內核空間的緩沖區(qū)中,拷貝完成之后函數就返回了,在后面的某個時刻,內核會將其緩沖區(qū)中的數據寫入(刷新)到磁盤設備中
    • 由此可知,系統調用 write() 與磁盤操作并不是同步的,write() 函數并不會等待數據真正寫入到磁盤之后再返回
    • 如果在此期間,其它系統調用如 read() 函數讀取該文件的這幾個字節(jié)數據,那么內核將自動從緩沖區(qū)中讀取這幾個字節(jié)數據返回給應用程序

    對于讀文件而言亦是如此,內核會從磁盤設備中讀取文件數據并存儲到內核緩沖區(qū)中,當調用 read() 讀取數據時,read() 調用將從內核緩沖區(qū)中讀取數據,直至把緩沖區(qū)中的數據讀完,這時,內核會將文件的下一段內容讀入到內核緩沖區(qū)中進行緩存,把這個內核緩沖區(qū)就稱為文件 I/O 的內核緩沖


  • 文件 I/O 的內核緩沖區(qū)的設計目的?
    • 1. 提高文件 I/O 的速度和效率(使得系統調用 read()、write()的操作更為快速)
      • 不需要等待磁盤操作(將數據寫入到磁盤或從磁盤讀取出數據),磁盤操作通常是比較緩慢的
    • 2. 減少內核操作磁盤的次數
      • 如:線程 1 調用 write() 向文件寫入 “abcd”,線程 2 也調用 write() 向文件寫入 “1234”,這樣,數據 “abcd” 和 “1234” 都被緩存在內核緩沖區(qū)中,稍后內核會將它們一起寫入磁盤中,只發(fā)起一次磁盤操作請求

    文件 I/O 的內核緩沖區(qū)自然是越大越好,內核會分配盡可能多的內核來作為文件 I/O 的內核緩沖區(qū),但受限于物理內存的總量,操作越大的文件也要依賴于更大空間的內核緩沖區(qū)


10.2 刷新文件 I/O 的內核緩沖區(qū)

  • 強制將文件 I/O 內核緩沖區(qū)中緩存的數據寫入(刷新)到磁盤設備中,對于某些應用程序來說是很有必要的
    • 例如,應用程序在進行某操作之前,必須要確保前面步驟調用 write() 寫入到文件的數據已經真正寫入到了磁盤中,諸如一些數據庫的日志進程
    • 在 Ubuntu 系統下拷貝文件到 U 盤時:文件拷貝完成之后,通常在拔掉 U 盤之前,需要執(zhí)行 sync 命令進行同步操作,這個同步操作其實就是將文件 I/O 內核緩沖區(qū)中的數據更新到 U 盤硬件設備,所以如果在沒有執(zhí)行 sync 命令時拔掉 U 盤,很可能就會導致拷貝到 U 盤中的文件遭到破壞
10.2.1 控制文件 I/O 內核緩沖的系統調用
  • fsync() 函數

    • 系統調用 fsync() 將參數 fd 所指文件的內容數據和元數據寫入磁盤,只有在對磁盤設備的寫入操作完成之后,fsync() 函數才會返回
    • 元數據并不是文件內容本身的數據,而是一些用于記錄文件屬性相關的數據信息,如:文件大小、時間戳、權限等等信息,這里統稱為文件的元數據,這些信息也是存儲在磁盤設備中的
    #include <unistd.h>int fsync(int fd);
    
    • 示例
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>#define BUF_SIZE 4096
    #define READ_FILE "./rfile"
    #define WRITE_FILE "./wfile"static char buf[BUF_SIZE];int main(void) {int rfd, wfd;size_t size;/* 打開源文件 */rfd = open(READ_FILE, O_RDONLY);if (rfd < 0) {perror("open error");exit(-1);}/* 打開目標文件 */wfd = open(WRITE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0664);if (wfd < 0) {perror("open error");exit(-1);}/* 拷貝數據 */while((size = read(rfd, buf, BUF_SIZE)) > 0)write(wfd, buf, size);/* 對目標文件執(zhí)行 fsync 同步 */fsync(wfd);/* 關閉文件退出程序 */close(rfd);close(wfd);exit(0);
    }
    
  • fdatasync() 函數

    • 系統調用 fdatasync()與 fsync()類似,不同之處在于 fdatasync() 僅將參數 fd 所指文件的內容數據寫入磁盤,并不包括文件的元數據
    #include <unistd.h>int fdatasync(int fd);
    
  • sync() 函數

    • 系統調用 sync() 會將所有文件 I/O 內核緩沖區(qū)中的文件內容數據和元數據全部更新到磁盤設備中,該函數沒有參數、也無返回值,意味著它不是對某一個指定的文件進行數據更新,而是刷新所有文件 I/O 內核緩沖區(qū)
    #include <unistd.h>void sync(void);
    
10.2.2 控制文件 I/O 內核緩沖的標志
  • O_DSYNC 標志
    • 在調用 open() 函數時,指定 O_SYNC 標志,使得每個 write() 調用都會自動將文件內容數據和元數據刷新到磁盤設備中,其效果類似于在每個 write() 調用之后調用 fsync() 函數進行數據同步
    fd = open(filepath, O_WRONLY | O_SYNC);
    

在程序中頻繁調用 fsync()、fdatasync()、sync()(或者調用 open 時指定 O_DSYNC 或 O_SYNC 標志)對性能的影響極大,大部分的應用程序是沒有這種需求的

10.3 直接 I/O:繞過內核緩沖

  • Linux 允許應用程序在執(zhí)行文件 I/O 操作時繞過內核緩沖區(qū),從用戶空間直接將數據傳遞到文件或磁盤設備,把這種操作也稱為直接 I/O(direct I/O)或裸 I/O(raw I/O)

    • 例如,某應用程序的作用是測試磁盤設備的讀寫速率,那么在這種應用需要下,就需要保證 read/write 操作是直接訪問磁盤設備,而不經過內核緩沖
    • 對于大多數應用程序而言,使用直接 I/O 可能會大大降低性能
      • 直接 I/O 只在一些特定的需求場合,如:磁盤速率測試工具、數據庫系統等
    • 可針對某一文件或塊設備執(zhí)行直接 I/O,需要在調用 open() 函數打開文件時指定 O_DIRECT 標志
    fd = open(filepath, O_WRONLY | O_DIRECT);
    
  • 因為直接 I/O 涉及到對磁盤設備的直接訪問,所以在執(zhí)行直接 I/O 時,必須要遵守三個對齊限制要求

    • 應用程序中用于存放數據的緩沖區(qū),其內存起始地址必須以塊大小的整數倍進行對齊
    • 寫文件時,文件的位置偏移量必須是塊大小的整數倍
    • 寫入到文件的數據大小必須是塊大小的整數倍
  • 以上所說的塊大小指的是磁盤設備的物理塊大小,常見的塊大小包括 512 字節(jié)、1024 字節(jié)、2048 以及 4096 字節(jié),如何確定磁盤分區(qū)的塊大小?通常,Ubuntu 系統的根文件系統掛載在 /dev/sda1 磁盤分區(qū)下

    $ sudo tune2fs -l /dev/sda1 | grep "Block size"
    Block size:               4096
    

10.4 stdio 緩沖

  • 標準 I/O 是 C 語言標準庫函數,而文件 I/O 是系統調用,雖然標準 I/O 是在文件 I/O 基礎上進行封裝而實現,但在效率、性能上,標準 I/O 要優(yōu)于文件 I/O,原因在于標準 I/O 維護了自己的緩沖區(qū),稱為 stdio 緩沖區(qū)
    • 文件 I/O 內核緩沖,這是由內核維護的緩沖區(qū),而標準 I/O 所維護的 stdio 緩沖是用戶空間緩沖區(qū)
    • 當應用程序中通過標準 I/O 操作磁盤文件時,標準 I/O 函數會將用戶寫入或讀取文件的數據緩存在 stdio 緩沖區(qū),然后再一次性將 stdio 緩沖區(qū)中緩存的數據通過調用系統調用 I/O(文件 I/O)寫入到文件 I/O 內核緩沖區(qū)或拷貝到應用程序的 buf 中
10.4.1 對 stdio 緩沖進行設置
1. setvbuf() 函數
  • 調用 setvbuf() 庫函數可以對文件的 stdio 緩沖區(qū)進行設置,如:緩沖區(qū)的緩沖模式、緩沖區(qū)的大小、起始地址等

    #include <stdio.h>int setvbuf(FILE *stream, char *buf, int mode, size_t size);
    
  • stream

    • FILE 指針,用于指定對應的文件,每一個文件都可以設置它對應的 stdio 緩沖區(qū)
  • buf

    • 如果參數 buf 不為 NULL,那么 buf 指向 size 大小的內存區(qū)域將作為該文件的 stdio 緩沖區(qū),因為 stdio 庫會使用 buf 指向的緩沖區(qū),所以應該以動態(tài)(分配在堆內存,如 malloc)或靜態(tài)的方式在堆中為該緩沖區(qū)分配一塊空間,而不是分配在棧上的函數內的自動變量(局部變量)
    • 如果 buf 等于 NULL,那么 stdio 庫會自動分配一塊空間作為該文件的 stdio 緩沖區(qū)(除非參數 mode 配置為非緩沖模式)
  • mode:用于指定緩沖區(qū)的緩沖類型

    • _IONBF:不對 I/O 進行緩沖(無緩沖)。意味著每個標準 I/O 函數將立即調用 write() 或者 read(),并且忽略 buf 和 size 參數,可以分別指定兩個參數為 NULL 和 0。標準錯誤 stderr 默認屬于這一種類型,從而保證錯誤信息能夠立即輸出
    • _IOLBF:采用行緩沖 I/O。這種情況下,當在輸入或輸出中遇到換行符 “\n” 時,標準 I/O 才會執(zhí)行文件 I/O 操作
      • 對于輸出流,在輸出一個換行符前將數據緩存(除非緩沖區(qū)已經被填滿),當輸出換行符時,再將這一行數據通過文件 I/O write() 函數刷入到內核緩沖區(qū)中
      • 對于輸入流,每次讀取一行數據。對于終端設備默認采用的就是行緩沖模式,譬如標準輸入和標準輸出
    • _IOFBF:采用全緩沖 I/O。這種情況下,在填滿 stdio 緩沖區(qū)后才進行文件 I/O 操作(read、write)
      • 對于輸出流,當 fwrite 寫入文件的數據填滿緩沖區(qū)時,才調用 write() 將 stdio 緩沖區(qū)中數據刷入內核緩沖區(qū)
      • 對于輸入流,每次讀取 stdio 緩沖區(qū)大小個字節(jié)數據。默認普通磁盤上的常規(guī)文件常用這種緩沖模式
  • size:指定緩沖區(qū)的大小

  • 返回值:成功返回 0,失敗將返回一個非 0 值,并設置 errno 指示錯誤原因

當 stdio 緩沖區(qū)中的數據被刷入到內核緩沖區(qū)或被讀取之后,這些數據就不會存在于緩沖區(qū)中了,數據被刷入了內核緩沖區(qū)或被讀走了

2. setbuf() 函數
  • setbuf() 函數構建于 setvbuf() 之上,執(zhí)行類似的任務
    #include <stdio.h>void setbuf(FILE *stream, char *buf);
    
  • setbuf() 調用除了不返回函數結果(void)外,就相當于
    // 要么將 buf 設置為 NULL 以表示無緩沖
    // 要么指向由調用者分配的 BUFSIZ 個字節(jié)大小的緩沖區(qū)(BUFSIZ 定義于頭文件 <stdio.h> 中,該值通常為 8192)
    setvbuf(stream, buf, buf ? _IOFBF : _IONBF, BUFSIZ);
    
3. setbuffer() 函數
  • setbuffer() 函數類似于 setbuf(),但允許調用者指定 buf 緩沖區(qū)的大小
    #include <stdio.h>void setbuffer(FILE *stream, char *buf, size_t size);
    
  • setbuffer() 調用除了不返回函數結果(void)外,就相當于
    setvbuf(stream, buf, buf ? _IOFBF : _IONBF, size);
    
10.4.2 標準輸出 printf() 的行緩沖模式測試
  • 標準 printf() 輸出測試
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>int main(void) {printf("Hello World!\n");printf("Hello World!");for ( ; ; )sleep(1);
    }
    
    $ gcc io2.c -o io2
    $ ./io2
    Hello World!  # 只有第一個 printf() 打印的信息顯示出來了,第二個并沒有顯示出來

這就是 stdio 緩沖的問題,標準輸出默認采用的是行緩沖模式,printf() 輸出的字符串寫入到了標準輸出的 stdio 緩沖區(qū)中,只有輸出換行符時(不考慮緩沖區(qū)填滿的情況)才會將這一行數據刷入到內核緩沖區(qū),也就是寫入標準輸出文件(終端設備)

  • 第一個 printf 包含了換行符,所以已經刷入了內核緩沖區(qū)
  • 第二個 printf 沒有包含換行符,所以輸出的 “Hello World!” 還緩存在 stdio 緩沖區(qū)中,需要等待一個換行符才可輸出到終端

  • 將標準輸出配置為無緩沖模式
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>int main(void) {/* 將標準輸出設置為無緩沖模式 */if (setvbuf(stdout, NULL, _IONBF, 0)) {perror("setvbuf error");exit(0);}printf("Hello World!\n");printf("Hello World!");for ( ; ; )sleep(1);
    }
    
    $ gcc io3.c -o io3
    $ ./io3
    Hello World!
    Hello World!
10.4.3 刷新 stdio 緩沖區(qū)
  • 無論采取何種緩沖模式,在任何時候都可以使用庫函數 fflush() 來強制刷新(將輸出到 stdio 緩沖區(qū)中的數據寫入到內核緩沖區(qū))stdio 緩沖區(qū),該函數會刷新指定文件的 stdio 輸出緩沖區(qū)
    #include <stdio.h>// 參數 stream 指定需要進行強制刷新的文件,如果該參數設置為 NULL,則表示刷新所有的 stdio 緩沖區(qū)
    int fflush(FILE *stream);
    
  • 使用 fflush() 刷新 stdio 緩沖區(qū)
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>int main(void) {printf("Hello World!\n");printf("Hello World!");fflush(stdout);  // 刷新標準輸出 stdio 緩沖區(qū)for ( ; ; )sleep(1);
    }
    
    $ gcc io4.c -o io4
    $ ./io4
    Hello World!
    Hello World!
    

  • 使用庫函數 fflush() 是一種強制刷新的手段,在一些其它的情況下,也會自動刷新 stdio 緩沖區(qū)
    • 關閉文件時系統自動刷新 stdio 緩沖區(qū)
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>int main(void) {printf("Hello World!\n");printf("Hello World!");fclose(stdout);  // 關閉標準輸出for ( ; ; )sleep(1);
    }
    

    上面的測試程序中,在最后都使用了一個 for 死循環(huán),讓程序處于休眠狀態(tài)無法退出,為什么要這樣做呢?原因在于程序退出時也會自動刷新 stdio 緩沖區(qū),這樣的話就會影響到測試結果,下面去掉 for 死循環(huán),讓程序結束

    • 程序退出時系統自動刷新 stdio 緩沖區(qū)
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>int main(void) {printf("Hello World!\n");printf("Hello World!");
    }
    
    $ gcc io5.c -o io5
    $ ./io5
    Hello World!
    Hello World! $
    

如果使用 exit()、return 或像上述示例代碼一樣不顯式調用相關函數或執(zhí)行 return 語句來結束程序,這些情況下程序終止時會自動刷新 stdio 緩沖區(qū),但是如果使用 _exit 或 _Exit() 終止程序則不會自動刷新 stdio 緩沖區(qū)

11. 文件描述符與 FILE 指針互轉

  • 在同一個文件上執(zhí)行 I/O 操作時,可以將文件 I/O(系統調用 I/O)與標準 I/O 混合使用,這個時候就需要將文件描述符和 FILE 指針對象之間進行轉換,此時可以借助于庫函數 fdopen()、fileno() 來完成
    • 庫函數 fileno() 可以將標準 I/O 中使用的 FILE 指針轉換為文件 I/O 中所使用的文件描述符
    • 而 fdopen() 則進行著相反的操作
    #include <stdio.h>int fileno(FILE *stream);
    FILE *fdopen(int fd, const char *mode);
    
  • 對于 fileno() 函數來說
    • 根據傳入的 FILE 指針得到整數文件描述符,通過返回值得到文件描述符
    • 如果轉換錯誤將返回 -1,并且會設置 errno 來指示錯誤原因
    • 得到文件描述符之后,便可以使用諸如 read()、write()、lseek()、fcntl()等文件 I/O 方式操作文件
  • fdopen() 函數與 fileno() 功能相反
    • 給定一個文件描述符,得到該文件對應的 FILE 指針
    • 之后便可以使用諸如 fread()、fwrite() 等標準 I/O 方式操作文件了

當混合使用文件 I/O 和標準 I/O 時,需要特別注意緩沖的問題

  • 文件 I/O 會直接將數據寫入到內核緩沖區(qū)進行高速緩存
  • 標準 I/O 則會將數據寫入到 stdio 緩沖區(qū),之后再調用 write() 將 stdio 緩沖區(qū)中的數據寫入到內核緩沖區(qū)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main(void) {printf("print");  // 缺換行符 "\n"write(STDOUT_FILENO, "write\n", 6);exit(0);
}
$ gcc test.c -o test
$ ./test 
write   # 先輸出了 "write" 字符串信息,接著再輸出了 "print" 字符串信息
print $
http://aloenet.com.cn/news/30201.html

相關文章:

  • 做任務懸賞網站百度網頁版登錄入口官網
  • 企業(yè)網盤是什么優(yōu)化關鍵詞排名seo
  • 源碼網站下載網絡推廣培訓去哪里好
  • 無錫大型網站建設公司谷歌網站優(yōu)化
  • 上海鴻鵠設計公司seo頁面內容優(yōu)化
  • 安徽做政府網站的企業(yè)日結app推廣聯盟
  • 做網站需要公司嗎建網站軟件工具
  • 濟南網站建設方案托管福州百度推廣排名優(yōu)化
  • 一個用戶注冊的網站怎么做網絡營銷的特點有哪些
  • 微網站的鏈接怎么做的網站推廣蘇州
  • 微信公眾號鏈接的網站怎么做南寧seo網絡推廣
  • 金融網站開發(fā)文檔百度小說搜索風云排行榜
  • 代理機構做的網站找不到人了怎么辦谷歌瀏覽器 安卓下載
  • 企業(yè)網站建設商城建網站的流程
  • 美食網站怎么做web網站模板
  • 如皋做公司網站杭州關鍵詞優(yōu)化測試
  • .net網站模板搭建網站需要哪些步驟
  • 公司注冊網上核名多久seo推廣工具
  • web項目開發(fā)seo工作
  • 建材網站建設功能方案汽車網絡營銷策劃方案
  • 一個企業(yè)做網站需要什么資料免費下優(yōu)化大師
  • 合肥優(yōu)化網站哪家公司好拼多多關鍵詞排名查詢工具
  • 企業(yè)網站模板演示seo外包如何
  • 做網站推廣優(yōu)化淘寶排名查詢工具
  • 攝影網站的市場可行性店鋪推廣
  • 建網站的公司大全開創(chuàng)集團與百度
  • 深圳微網站建設今日油價92汽油
  • 婚戀網站系統人工智能培訓機構排名前十
  • 天津專業(yè)網站制作流程優(yōu)勢網站搜索關鍵詞優(yōu)化
  • 西安高校網站建設搜索網站有哪些