4.lseek函數及共享文件


4.1.lseek函數介紹
(1)當我們要對1個文件進行讀寫時,首先需打開該文件,則我們讀寫的所有文件都是動態文件;動態文件在內存中就是以文件流的形式存在的。
(2)文件流很長,裏面有很多個字符,我們需要確定當前正在操作的是哪個位置;GUI模式下的軟件用光標來標識當前正在操作文件流的哪個位置;在動態文件中則通過文件指針(即Vnode結構體中的1個元素)來標識當前正在操作文件流哪個位置;文件指針不能被直接訪問,linux系統使用lseek函數來訪問該文件指針。
(3)當我們打開1個空文件時,默認情況下文件指針指向文件流的起始位置,則這時候去讀寫該文件時就是從文件流的起始位置開始的;write和read函數本身自帶移動文件指針的功能,所以當我讀寫了n個字節後,文件指針會自動向後移動n位;如果需要人爲的隨意更改文件指針,那就只能通過lseek函數了。
(4)read和write函數都是從當前文件指針處開始操作的,所以當我們用lseek顯式的將文件指針移動後,那麼再去read/write時就是從移動過後的位置開始的;則當我們操作空文件時先write寫了12字節,然後read時是空的,但是此時我們打開文件後發現12字節確實寫進來了。


4.2.計算文件長度和構建空洞文件
(1)linux中並沒有1個函數可以直接返回1個文件的長度,但是我們做項目時經常會需要知道1個文件的長度,我們自己利用lseek來寫1個函數得到文件長度即可。
(2)空洞文件即該文件中有1段是空的;普通文件中間是不能有空的,因爲我們write時文件指針是依次從前到後去移動的,我們不可能繞過前面直接到後面;我們打開某個文件後,用lseek往後跳過1段,再write寫入1段,則會構成1個空洞文件。
(3)空洞文件方法對多線程共同操作文件是及其有用的,有時候我們創建1個很大的文件,如果從頭開始依次構建時間很長,有1種思路就是將文件分爲多段,然後多線程來操作,每個線程負責其中1段的寫入。


4.3.重複打開同一文件讀取
(1)1個進程中兩次打開同一個文件,然後分別讀取,1種情況是fd1和fd2分別讀,1種情況是接續讀;經過實驗驗證,證明了結果是fd1和fd2分別讀。
(2)分別讀說明我們使用open兩次打開同一個文件時,fd1和fd2所對應的文件指針(fd->文件表指針->文件表->文件指針)是不同的2個獨立的指針;文件指針是包含在文件表中的,所以可以看出linux系統的進程中不同fd對應的是不同的獨立的文件表。


4.4.重複打開同一文件寫入
(1)1個進程中兩次打開同一個文件,然後分別寫入,1種情況是fd1和fd2分別寫,1種情況是接續寫;經過實驗驗證,證明了結果是fd1和fd2分別寫。
(2)正常情況下我們有時候需要分別寫,有時候又需要接續寫,所以這兩種本身是沒有好壞之分的,關鍵看用戶需求;有時候我們希望接續寫而不是分別寫,辦法就是在open時加O_APPEND標誌即可。


4.5.O_APPEND實現原理及原子操作性說明
(1)分別寫的內部原理就是2個fd擁有不同的文件指針,並且彼此只考慮自己的位移;但是O_APPEND標誌可以讓write和read函數內部多做1件事情,即移動自己的文件指針的同時也移動別人的文件指針(即fd1和fd2還是各自擁有1個獨立的文件指針,但是這兩個文件指針關聯起來了,1個動了會通知另1個跟着動)。
(2)原子操作的含義即整個操作一旦開始是不會被打斷的,必須直到操作結束其它代碼才能得以調度運行,這就叫原子操作;每種操作系統中都有一些機制來實現原子操作,以保證那些需要原子操作的任務可以運行;O_APPEND對文件指針的影響,對文件的讀寫是原子的。


4.6.文件共享及實現方式
(1)文件共享即同一個文件(即同一個inode,同一個pathname)被多個獨立的讀寫體(即多個文件描述符)同時(一個已打開尚未關閉的同時另一個去操作)操作;文件共享的意義有很多,譬如我們可以通過文件共享來實現多線程同時操作同1個大文件,以減少文件讀寫時間,提升效率。
(2)文件共享的核心即製造多個文件描述符指向同一個文件;常見的有3種文件共享的情況,第1種是同一個進程中多次使用open打開同一個文件;第2種是在不同進程中去分別使用open打開同一個文件(兩個fd在不同的進程中,則兩個fd的數字可以相同也可以不同);第3種情況是linux系統提供了dup和dup2兩個API來讓進程複製文件描述符;分析文件共享時的核心關注點在於確認是分別寫/讀還是接續寫/讀。
(3)當兩個文件指針分別獨立且互不關聯->分別寫/讀;當兩個文件指針分別獨立且相互關聯/兩個文件指針相同->接續寫/讀(見圖1)。


4.7.再論文件描述符
(1)文件描述符的本質是1個數字,該數字本質上是進程表中文件描述符表的1個表項,進程通過文件描述符作爲index去索引查表得到文件表指針,再間接訪問得到該文件對應的文件表。
(2)文件描述符是open系統調用內部由操作系統自動分配的,操作系統規定fd從0開始依次增加;fd也是有最大限制的,在linux的早期版本中(0.11)fd最大是20,所以當時1個進程最多允許打開20個文件;linux中文件描述符表是個指針數組(不是鏈表),其中fd是index,文件表指針是value。
(3)當我們去open時,內核會從文件描述符表中挑選1個最小的未被使用的數字給我們返回;即如果之前fd已經佔滿了0-9,那我們下次open得到的一定是10(但是如果上一個fd得到的是9,下一個不一定是10,這是因爲可能前面更小的一個fd已經被close釋放掉了)。
(4)fd中0、1、2已經默認被系統內核佔用了,因此用戶進程得到的最小的fd就是3了;當我們運行1個程序得到1個進程時,內部默認已打開3個文件,其對應的fd就是0、1、2;這3個文件分別叫stdin、stdout、stderr,即標準輸入、標準輸出、標準錯誤。
(5)標準輸入一般對應的是鍵盤(0這個fd對應的是鍵盤的設備文件),標準輸出一般是LCD顯示器(1對應LCD的設備文件);printf函數其實就是默認輸出到標準輸出stdout上了,fpirntf函數可以指定輸出到哪個文件描述符中。


這裏寫圖片描述


4.lseek_example
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 項目:lseek函數及共享文件
 * 功能:演示lseek函數的基本使用。
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    int fd = -1;                                // fd即file descriptor,文件描述符     
    int ret = -1;                               // 用於接收read返回值,判斷是否讀文件成功
    char buf[100] = {0};                        // 構建緩衝區存放從文件讀出的內容
    char bufwrite[20] = "linux is great";       // 構建緩衝區存放要寫入文件的內容

#if 0   
    // 打開test.txt文件,若該文件不存在則創建(權限666),若該文件存在則報錯
    fd = open("test.txt", O_RDWR | O_CREAT | O_EXCL, 0666); 
#else
    // 打開test.txt文件,若文件不存在則報錯
    fd = open("test.txt", O_RDONLY);
#endif  
    if (-1 == fd)                               // 判斷文件打開是否成功,也可這樣寫if (fd < 0)
    {
        //printf("open file error.\n");
        perror("open file error");              // 通過perror捕捉errno錯誤信息並打印輸出
        //return -1;                            // 使用return或exit退出進程或程序
        exit(-1);
    }
    else
    {
        printf("open file sucess. fd = %d.\n", fd);
    }

#if 1   
    ret = lseek(fd, 3, SEEK_SET);               // 使用lseek函數從文件開始位置開始向後移動3個字符位置
    printf("lseek, ret = %d.\n", ret);
#endif

#if 0   
    ret = write(fd, bufwrite, strlen(bufwrite));    // 寫內容到文件中
    if (ret < 0)                                    // 判斷內容是否成功寫入文件
    {
        //printf("write file errot.\n");
        perror("write file errot");
        //return -1;
        exit(-1);
    }
    else
    {
        printf("write %d bytes to file.\n", ret);
        printf("the content of write is [%s].\n", bufwrite);
    }
#endif

#if 1   
    ret = read(fd, buf, sizeof(buf));   // 讀取文件內容
    if (ret < 0)                        // 判斷讀取文件是否成功
    {
        //printf("read file error.\n");
        perror("read file error");
        //return -1;
        exit(-1);
    }
    else
    {
        printf("read %d bytes actual from file.\n", ret);
        printf("the content of read is [%s].\n", buf);
    }
#endif

    close(fd);                          // 關閉文件

    //return 0;
    exit(0);
}

4.lseek_callen
 /*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 項目:lseek函數及共享文件
 * 功能:使用lseek函數測試某個文件的大小。
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

/* 測試文件的大小,形參爲路徑名 */
int callen(const char *pathname)
{
    int fd = -1, ret = -1;

    fd = open(pathname, O_RDONLY);
    if (-1 == fd)
    {
        perror("open file error");
        exit(-1);
    }

    // lseek將文件指針移動到末尾,返回值即文件指針距離文件開頭的偏移量,即文件的長度
    ret = lseek(fd, 0, SEEK_END);
    return ret;
}

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        printf("usage: %s pathname.\n", argv[0]);
        exit(-1);
    }

    printf("The size of %s is %d.\n", argv[1], callen(argv[1]));

    exit(0);
}

4.lseek_empty
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 項目:lseek函數及共享文件
 * 功能:使用lseek構建1個空洞文件。
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    int fd = -1;                                // fd即file descriptor,文件描述符     
    int ret = -1;                               // 用於接收read返回值,判斷是否讀文件成功
    char buf[100] = {0};                        // 構建緩衝區存放從文件讀出的內容
    char bufwrite[20] = "linux is great";       // 構建緩衝區存放要寫入文件的內容

    // 打開test.txt文件,若該文件不存在則創建(權限666),若該文件存在則報錯
    fd = open("test.txt", O_RDWR | O_CREAT | O_EXCL, 0666); 
    if (-1 == fd)                               // 判斷文件打開是否成功,也可這樣寫if (fd < 0)
    {
        perror("open file error");              // 通過perror捕捉errno錯誤信息並打印輸出
        exit(-1);
    }

#if 1   
    ret = lseek(fd, 10, SEEK_SET);              // 使用lseek函數從文件開始位置開始向後移動10個字符位置
    printf("lseek, ret = %d.\n", ret);          // 即構建了1個空洞文件
#endif

#if 1   
    ret = write(fd, bufwrite, strlen(bufwrite));// 寫內容到文件中
    if (ret < 0)                                // 判斷內容是否成功寫入文件
    {
        perror("write file errot");
        exit(-1);
    }
#endif

    close(fd);                                  // 關閉文件

    exit(0);
}

4.multi_read
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 項目:lseek函數及共享文件
 * 功能:1個進程重複打開同一個文件讀取。
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    int fd1 = -1, fd2 = -1;                     // fd即file descriptor,文件描述符     
    int ret = -1;                               // 用於接收read返回值,判斷是否讀文件成功
    char buf[100] = {0};                        // 構建緩衝區存放從文件讀出的內容

    fd1 = open("test.txt", O_RDONLY);
    fd2 = open("test.txt", O_RDONLY);
    if ((-1 == fd1) || (-1 == fd2))             // 判斷文件打開是否成功,也可這樣寫if (fd < 0)
    {
        perror("open file error");              // 通過perror捕捉errno錯誤信息並打印輸出
        exit(-1);
    }

    while (1)
    {
        memset(buf, 0, sizeof(buf));
        ret = read(fd1, buf, 2);                // 讀取文件內容
        if (ret < 0)                            // 判斷讀取文件是否成功
        {
            perror("read file error");
            exit(-1);
        }
        else
        {
            printf("the content of read from fd1 is [%s].\n", buf);
        }

        sleep(1);

        memset(buf, 0, sizeof(buf));
        ret = read(fd2, buf, 2);                // 讀取文件內容
        if (ret < 0)                            // 判斷讀取文件是否成功
        {
            perror("read file error");
            exit(-1);
        }
        else
        {
            printf("the content of read from fd2 is [%s].\n", buf);
        }
    }

    close(fd1);                                 // 關閉文件
    close(fd2);

    exit(0);
}

4.multi_write
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 項目:lseek函數及共享文件
 * 功能:1個進程重複打開同一個文件寫入。
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    int fd1 = -1, fd2 = -1;                     // fd即file descriptor,文件描述符     
    int ret = -1;                               // 用於接收read返回值,判斷是否讀文件成功

    // 若文件存在則原來的內容消失,若文件不存在則以權限666創建該文件
#if 0   
    fd1 = open("test.txt", O_RDWR | O_TRUNC | O_CREAT , 0666);              // 分別寫
    fd2 = open("test.txt", O_RDWR | O_TRUNC | O_CREAT , 0666);
#else
    fd1 = open("test.txt", O_RDWR | O_TRUNC | O_CREAT | O_APPEND, 0666);    // 接續寫
    fd2 = open("test.txt", O_RDWR | O_TRUNC | O_CREAT | O_APPEND, 0666);
#endif
    if ((-1 == fd1) || (-1 == fd2))             // 判斷文件打開是否成功,也可這樣寫if (fd < 0)
    {
        perror("open file error");              // 通過perror捕捉errno錯誤信息並打印輸出
        exit(-1);
    }

    while (1)
    {
        ret = write(fd1, "ab", 2);              // 寫內容到文件中
        if (ret < 0)                            // 判斷內容是否成功寫入文件
        {
            perror("write file errot");
            exit(-1);
        }
        else
        {
            printf("the content of write from fd1 is [%s].\n", "ab");
        }

        sleep(1);

        ret = write(fd2, "cd", 2);              // 寫內容到文件中
        if (ret < 0)                            // 判斷內容是否成功寫入文件
        {
            perror("write file errot");
            exit(-1);
        }
        else
        {
            printf("the content of write from fd2 is [%s].\n", "cd");
        }
    }

    close(fd1);                                 // 關閉文件
    close(fd2);

    exit(0);
}

發佈了104 篇原創文章 · 獲贊 29 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章