LinuxC——1.文件讀寫


LinuxC——1.文件讀寫

1.❤️文件IO

從CPU到文件是Output的一個過程,從文件到CPU是一個Input的過程,這個過程是以CPU為點的

2.🧡系統函數

  • open:打開文件
  • close:關閉文件
  • read:讀數據
  • write:寫數據
  • lseek:移動文件中讀寫位置
  • dup:文件書寫位置重定位函數,重定位可以寫入另一個文件
  • fcntl:文件描述符設置
  • ioctl:一個特殊函數

3.💛文件讀寫的簡單例子

  • open函數:通過fd,找到塊設備文件
    • 文件系統是一個程序代碼,組織塊設備所有文件
    • 文件系統屬於OS一部分
    • 找到文件后,調用塊設備驅動,打開文件
      • 打開成功,返回非負操作符
      • 打開失敗,返回-1
  • write函數:利用打開成功返回的,向文件里面寫數據
  • lseek函數:利用文件描述符,將文件讀寫位置調整到文件相應位置
    • why設置文件頭
    • write的時候,文件讀寫位置已經到了末尾
  • read函數:從文件頭開始,讀取指定長度的數據到buf中
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main() {
        int fd = 0;

        fd = open("./file.txt", O_RDWR);
        if(-1 == fd) {
                printf("open fail\n");
                return 1;
        }else {
                printf("open ok\n");
        }

        char buf[] = "Steve Yu, today is nice";
        write(fd, (void *)buf, strlen(buf));

        lseek(fd, SEEK_SET, 0);

        char buf2[30] = {0};
        read(fd, buf2, sizeof(buf));

        puts(buf2);
  
  			close(fd);
        return 0;
}

4.💚文件打開后,OS做了什么?

  1. 記錄打開文件信息

    1. 程序運行后是一個進程,OS會創建一個task_struct的結構體,記錄運行的各種信息
    2. open將文件打開后,在task_struct會創建一些數據結構,用來創建當前進程的打開文件的信息,后面所有的都依賴這些信息
  2. open函數會申請一段內存空間(內核緩存)

    1. 內核緩存是在OS的一個空間,比如char buf[100],就開辟了緩存
    2. 由os開辟的叫內核緩存
  3. open為啥要開辟內存緩存空間?

    內存讀寫 > 磁盤讀寫,我們會省時間

  4. open僅僅開辟了內存緩存空間,並沒有數據,只有讀寫數據的時候

    先將數據讀到內核緩存中,之后下層會讓內存緩存和磁盤數據進行交換

  5. 讀寫的時候,數據的流動?

    硬件 -> 驅動緩存 -> 內核緩存 -> 應用程序中的數組

(僅掌握open函數,其余函數已經過時)

5.💙指定宏

  1. O_RDONLY:只讀
  2. O_WRONLY:只寫
  3. O_RDWR:可讀可寫

以上三個不可以用 | 進行宏運算,而可以與以下進行宏運算

  1. O_TRUNC:打開進行清空
  2. O_APEND:打開后寫數據追加
  3. O_NONBLOCK和O_AYNC:非阻塞,同步

flag參數之O_CREAT、O_EXCL

  1. O_CREAT:新建一個文件
  2. O_EXCL:O_EXCL和O_CREAT同時被指定,打開文件,如果文件存在,就報錯

可以使用O_RDWR|O_CREAT進行組合,那么可以不存在進行創建,存在不打開的功能

6.💜文件描述符

  1. 文件描述符是什么?

文件描述符指向打開的文件,后面read/write/close等操作,都是基於文件描述符進行操作

  1. 文件描述符池

每個程序運行起來后,就是一個進程,系統給每個進程分配01023的描述符的范圍,返回的文件描述符,是在01023的某個數字

  1. 為啥第一個打開的文件,open返回的是3?

open返回的文件描述符是規則的:

  • open返回文件描述符池中,返回當前沒用的最小的一個

  • 進程運行起來,0/1/2會默認使用,最小沒用的是3

  1. fopen文件指針

fopen是C庫標准io函數

fopen成功后,FILE*的文件指針,打開了文件

fopen成功后,返回的是FILE*文件指針,指向打開的文件

  1. 對於Linux C庫,fopen對open進行二次封裝

7.❤️errno和perror

Linux中,如果像上述中,打開失敗,那么就直接失敗,遇到比較難排查的錯誤原因,難以查處具體錯誤

  1. 什么是errno?

errno我們查看man 3 errno中,可以看到,一系列錯誤宏定義,我們include "errno.h"即可

printf("%d: open error", errno);

這樣即可發現17:open error,打開錯誤

2.具體錯誤

我們僅僅知道錯誤號,不知道具體錯誤,怎么辦呢?

  • perror函數:我們通過man 3 perror,可以查看到錯誤號的具體使用

    perror可以打印的錯誤號以及具體錯誤

perror("open fail");

我們可以知道,控制台打印:open fail: File exists

3.使用man 2 open進行查看ERRORS下錯誤號

比如EACCES,不允許訪問

我們因為記不住錯誤號,也記不住宏定義,所以,我們有了perror

8.🧡close、write、read

  1. close函數

close(fd):不主動關閉,應用也會自動關閉fd,但是我們得進行手動關閉

  1. close函數做了啥
  • open打開的時候會在task struct中創建結構體空間,如果文件關閉,那么該結構體空間被釋放

    類似free(空間地址)

  • malloc和free是給C調用的庫看書,Linux在釋放的時候,有自己的釋放函數

好習慣必須關閉,否則我們在生產環境,容易內存溢出

9.💛task struct結構體

  1. 結構體在存在在運行的進程中,在task struct是程序在運行的時候進行開辟

  2. 進程運行結束,linux系統會調用自己的系統函數,進行釋放task struct

10.💚文件描述符

有關0/1/2文件描述符

  • stdin,文件描述符為0,0代表鍵盤,實現鍵盤輸入

鍵盤->鍵盤驅動緩存->內核緩存->應用緩存

#include <unistd.h>
#include <stdio.h>
int main() {
        int ret = 0;
        char buf[30] = {0};
        ret = read(0, (void *)buf, 30);
        if(-1 == ret) {
                perror("read fail");
                return -1;
        }
        puts(buf);
        return 0;
}

scanf底層調用read(0, buf, size)這個,這樣就能兼容不同操作系統

  • close(0),scanf還能工作不?

答案是不可以,用perror打印,會得到bad description的

  • stdout,文件描述符為1,使用1,則可以進行顯示器顯示
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main() {
        int fd = 1, ret = 0;

        char buf[] = "steve\n";
        write(fd, buf, sizeof(buf));

        close(fd);
        return 0;
}

假設我們write(1, &a, sizeof(a)), a是一個int值,那么會打印一個(char)a進行

  • close(1)

printf不能工作

  • stderr, 文件描述符是2,是標准出錯輸出

使用2文件描述符也能進行打印

1打印普通信息,2打印報錯信息

  • close(2)

perror是通過2進行打印,如果close(2),則不能進行perror

  • 宏定義

0:STDIN_FILENO

1:STDOUT_FILENO

2:STDERR_FILENO

11.💙lseek

lseek用來調整讀寫的位置,調用成功返回偏移量,調用失敗返回-1

我們可以通過lseek獲取文件大小

1.fd:打開文件

2.whence:粗定位

SEEK_SET:起始位置

SEEK_CUR:當前讀寫位置

SEEK_END:文件末尾位置

3.offset:微調

進行偏移,正數向前移動,負數向后

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

int main() {
        int fd = 0, ret = 0;

        fd = open("./file.txt", O_RDONLY);

        if(-1 == fd) {
                perror("file open failed");
                exit(-1);
        }

        ret = lseek(fd, 0, SEEK_END);

        printf("文件大小%d\n", ret);
        return 0;
}

4.使用od -c filename,可以查看以字符進行顯示字符

12.💜進程表和文件描述表

進程表:task_struct

  • 這個結構體成員多達300個
  • 每個進程運行起來后,linux系統會開辟task_struct 結構體
  • task_struct專門用於進程運行中,涉及進程相關信息

13.❤️共享操作文件

  • 同一個進程共享相同文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
void print_error(char* str) {
        perror(str);
        exit(-1);
}
int main() {
        int fd1, fd2;

        fd1 = open("./file.txt", O_RDWR);

        if(-1 == fd1) print_error("open fail");

        fd2 = open("./file.txt", O_RDWR);

        if(-1 == fd2) print_error("open fail");
  
  			// 寫入操作
        return 0;
}

存在相互覆蓋的關系

解決方案:使用O_APPEND進行追加

  • 多個進程共享相同文件
gcc share_op_file.c -o a
gcc share_op_file.c -o b
./a
./b

那么會出現覆蓋情況

解決方案:也使用O_APPEND進行追加

14.🧡dup和dup2

  1. int dup(int fd)
  • 在unistd.h中

  • dup用來復制文件描述符,得到一個新的文件描述符,指向原來的文件,指向最小沒用的那一個

fd2 = dup(fd1)
  1. int dup2(int oldfd, int newfd)
  • 在unistd.h中
  • dup2指定一個文件描述符,用來復制文件描述符,得到一個新的文件描述符,如果已經打開,則關閉后再次打開
fd2 = (fd1, 4)
  1. dup、dup2的意義

實現文件共享操作,不使用APPEND也不會出現文件覆蓋。使用dup或dup2的時候,永遠只有一個文件表。

15.💛實現文件重定位

步驟:

  1. 打開文件fd

  2. close(1)

  3. 進行復制fd到1

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
        int fd1;
        fd1 = open("data.txt", O_RDWR|O_CREAT);
        close(1);
        dup(fd1);
        printf("Hello World");
        return 0;
}

當我們文件描述符寫死了,那么可以進行文件重定位來進行,linux底層的重定位>是使用dup/dup2作為底層的支持

16.fcntl函數

int fcntl(int fd, int cmd, ... /* arg */ );

fcntl是文件控制函數(file control)

  • fd:指向打開文件

  • cmd:控制命令

    • F_DUPFD
      • 模擬DUP
    • F_GETFL、F_SETFL
      • 獲取設置文件狀態標注,比如open沒有指定O_APPEND,可以使用fcntl進行補設
    • F_GETFD、F_SETFD(文件描述符)
    • F_GETOWN、F_SETOWN(OWN)
    • F_GETLK、F_SETLK、F_SETLKW(加鎖)

    先掌握前兩個F_DUPFDF_GETFL、F_SETFL

模擬DUP:

fcntl(fd, F_DUPFD, 0);// 第三個參數沒有,用0表示

模擬DUP2:

fcntl(fd, F_DUPFD, 1);// 模擬DUP2,進行用1

設置

fcntl(fd, F_SETFL, O_RDWR|O_APPEND); // 修改打開時的Flag,返回新設置的標志

保留原標志,加新標志

flag = fcntl(fd, F_GETFL); // 獲取
fcntl(fd, F_SETFL, flag|O_APPEND); // 疊加


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM