linux文件IO

系統(tǒng)調(diào)用

  • 所謂系統(tǒng)調(diào)用是指操作系統(tǒng)提供給用戶程序調(diào)用的一組“特殊”接口,用戶程序可以通過這組“特殊”接口來獲得操作系統(tǒng)內(nèi)核提供的服務(wù)。
  • 例如用戶可以通過進程控制相關(guān)的系統(tǒng)調(diào)用來創(chuàng)建進程、實現(xiàn)進程調(diào)度、進程管理等。
  • 為什么用戶程序不能直接訪問系統(tǒng)內(nèi)核提供的服務(wù)呢?
    這是由于在Linux中,為了更好地保護內(nèi)核空間,將程序的運行空間分為內(nèi)核空間和用戶空間(也就是常稱的內(nèi)核態(tài)和用戶態(tài)),它們分別運行在不同的級別上,在邏輯上是相互隔離的。因此,用戶進程在通常情況下不允許訪問內(nèi)核數(shù)據(jù),也無法使用內(nèi)核函數(shù),它們只能在用戶空間操作用戶數(shù)據(jù),調(diào)用用戶空間的函數(shù)。
  • 但是,在有些情況下,用戶空間的進程需要獲得一定的系統(tǒng)服務(wù)(調(diào)用內(nèi)核空間程序),這時操作系統(tǒng)就必須利用系統(tǒng)提供給用戶的“特殊接口”——系統(tǒng)調(diào)用規(guī)定用戶進程進入內(nèi)核空間的具體位置(方法:軟中斷+系統(tǒng)調(diào)用號)。進行系統(tǒng)調(diào)用時,程序運行空間需要從用戶空間進入內(nèi)核空間,處理完后再返回到用戶空間。

系統(tǒng)調(diào)用、用戶空間和內(nèi)核空間程序關(guān)系

1、Linux的虛擬地址空間為0~4G,Linux內(nèi)核將這4G字節(jié)的空間分為用戶空間和內(nèi)核空間兩部分。
2、內(nèi)核空間的虛擬地址范圍為0xC0000000到0xFFFFFFFF,該部分空間供內(nèi)核使用,存放內(nèi)核代碼及內(nèi)核數(shù)據(jù)。
3、用戶空間虛擬地址范圍為 0x00000000到0xBFFFFFFF,供用戶的各個進程使用,存放用戶代碼和用戶數(shù)據(jù)。
4、用戶進程無法訪問直接訪問內(nèi)核空間,必須通過API觸發(fā)內(nèi)核的軟中斷機制進行系統(tǒng)調(diào)用,以達到訪問內(nèi)核空間的目的。

API

  • 前面講到的系統(tǒng)調(diào)用并不是直接與程序員進行交互的,它僅僅是一個通過軟中斷機制向內(nèi)核提交請求,以獲取內(nèi)核服務(wù)的接口。在實際使用中程序員調(diào)用的通常是用戶編程接口——API
  • 系統(tǒng)命令相對API更高了一層,它實際上一個可執(zhí)行程序,它的內(nèi)部引用了用戶編程接口(API)來實現(xiàn)相應(yīng)的功能。


文件描述符

  • 內(nèi)核如何區(qū)分和引用特定的文件呢?這里用到了一個重要的概念——文件描述符。
  • 對于Linux而言,所有對設(shè)備和文件的操作都是使用文件描述符來進行的。
  • 文件描述符是一個非負的整數(shù),它是一個索引值,并指向在內(nèi)核中每個進程打開文件的記錄表。
  • 當(dāng)打開一個現(xiàn)存文件或創(chuàng)建一個新文件時,內(nèi)核就向進程返回一個文件描述符;當(dāng)需要讀寫文件時,也需要把文件描述符作為參數(shù)傳遞給相應(yīng)的函數(shù)。
  • 通常,一個進程啟動時,都會打開3個文件:標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)出錯處理。
    這3個文件分別對應(yīng)文件描述符為0、1和2(也就是宏替換STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO)。

底層文件I/O操作

  • open()函數(shù)是用于打開或創(chuàng)建文件,在打開或創(chuàng)建文件時可以指定文件的屬性及用戶的權(quán)限等各種參數(shù)。


  • close()函數(shù)是用于關(guān)閉一個被打開的文件。當(dāng)一個進程終止時,所有被它打開的文件都由內(nèi)核自動關(guān)閉,很多程序都使用這一功能而不顯示地關(guān)閉一個文件。


  • read()函數(shù)是用于將從指定的文件描述符中讀出的數(shù)據(jù)放到緩存區(qū)中,并返回實際讀入的字節(jié)數(shù)。若返回0,則表示沒有數(shù)據(jù)可讀,即已達到文件尾。讀操作從文件的當(dāng)前指針位置開始。當(dāng)從終端設(shè)備文件中讀出數(shù)據(jù)時,通常一次最多讀一行。


  • write()函數(shù)是用于向打開的文件寫數(shù)據(jù),寫操作從文件的當(dāng)前指針位置開始。對磁盤文件進行寫操作,若磁盤已滿或超出該文件的長度,則write()函數(shù)返回失敗。


  • lseek()函數(shù)是用于在指定的文件描述符中將文件指針定位到相應(yīng)的位置。它只能用在可定位(可隨機訪問)文件操作中。管道、套接字和大部分字符設(shè)備文件是不可定位的,所以在這些文件的操作中無法使用lseek()調(diào)用。


  • 舉一個復(fù)制文件的例子:

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

#define BUFFER_SIZE 1024
#define SRC_FILE_NAME "srcfile.txt"
#define DEST_FILE_NAME "destfile.txt"
#define OFFSET 1024*10

int main()
{
    int src_fd,dest_fd,real_read_long=0,rtn_num;
    char buffer[BUFFER_SIZE];
    long long int cpnum=0;
    memset(buffer,0,BUFFER_SIZE);
    //open file
    src_fd=open(SRC_FILE_NAME,O_RDONLY);
    dest_fd=open(DEST_FILE_NAME,O_RDWR|O_CREAT,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
    if(src_fd<0||dest_fd<0)
    {
        printf("open srcfile or destfile fail!\n");
        exit(1);
    }
    //move srcfile data pointer 
    rtn_num=lseek(src_fd,-OFFSET,SEEK_END);
    if(rtn_num==-1)
    {
        printf("move srcfile date pointer fail!\n");
        return 1;
    }
    printf("current srcfile pointer is %d\n",rtn_num);
    //copy srcfile data to destfile
    while(real_read_long=read(src_fd,buffer,BUFFER_SIZE))
    {
        cpnum+=real_read_long;//count copy bytes
        write(dest_fd,buffer,real_read_long);
    }
    printf("Copy finished!(size:%lld bytes)\n",cpnum);
    close(dest_fd);
    close(src_fd);
    
    return 0;
}

文件鎖

  • 舉例:
    1) 進程“A”打開和讀取一個文件,此文件包含賬戶相關(guān)的一些信息。
    2) 進程“B”也打開了這個文件,并讀取了文件中的信息。
    3) 現(xiàn)在,進程“A”更改了其副本中的一條余額記錄,并將其寫入文件。
    4) 此時,進程“B”并不知道上次讀取的文件已經(jīng)被更改,它還保存著原始的文件副本。然后,進程“B”更改了“A”操作的那條相同的記錄,并將記錄寫入文件。
    5) 此時,文件中將只保存了進程“B”更改過的記錄。
  • 為避免這種事情發(fā)生,就要用文件鎖來確保操作的“序列化”。
  • 文件鎖包括建議性鎖和強制性鎖。
    建議性鎖又叫協(xié)同鎖,它要求每個上鎖文件的進程都要檢查是否有鎖存在,并且尊重已有的鎖。在一般情況下,內(nèi)核和系統(tǒng)都不使用建議性鎖。
    強制性鎖是由內(nèi)核執(zhí)行的鎖,當(dāng)一個文件被上鎖進行寫入操作的時候,內(nèi)核將阻止其他任何文件對其進行讀寫操作。采用強制性鎖對性能的影響很大,每次讀寫操作都必須檢查是否有鎖存在。
  • 在Linux中,實現(xiàn)文件上鎖的函數(shù)有l(wèi)ockf()和fcntl(),其中l(wèi)ockf()用于對文件施加建議性鎖,而fcntl()不僅可以施加建議性鎖,還可以施加強制鎖。同時,fcntl()還能對文件的某一記錄上鎖,也就是記錄鎖。
    記錄鎖又可分為讀取鎖和寫入鎖,其中讀取鎖又稱為共享鎖,它能夠使多個進程都能在文件的同一部分建立讀取鎖。而寫入鎖又稱為排斥鎖,在任何時刻只能有一個進程在文件的某個部分上建立寫入鎖。當(dāng)然,在文件的同一部分不能同時建立讀取鎖和寫入鎖。
  • fcntl()函數(shù)格式(1)


  • fcntl()函數(shù)格式(2)- flock結(jié)構(gòu)
struct flock
{
    short l_type;
    off_t l_start;
    short l_whence;
    off_t l_len;
    pid_t l_pid;
}

文件鎖例子:
lock_set.c

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

void lock_set(int fd,short type)
{
    struct flock lock;
    lock.l_type=type;
    lock.l_start=0;
    lock.l_whence=SEEK_SET;
    lock.l_len=0;
    lock.l_pid=-1;
    
    fcntl(fd,F_GETLK,&lock);
    if(lock.l_type!=F_UNLCK)
    {
        if(lock.l_type==F_RDLCK)
            printf("The file %d has already F_RDLCK by process %d\n",fd,lock.l_pid);
        if(lock.l_type==F_WRLCK)
            printf("The file %d has already F_WRLCK by process %d\n",fd,lock.l_pid);
    }
    lock.l_type=type;
    if(fcntl(fd,F_SETLKW,&lock))
        printf("The file %d was faile to be set lock by process %d\n",fd,getpid());
        
    switch(lock.l_type)
    {
        case F_RDLCK:
            printf("The file %d was set readlock by process %d\n",fd,getpid());
            break;
        case F_WRLCK:
            printf("The file %d was set writelock by process %d\n",fd,getpid());
            break;
        case F_UNLCK:
            printf("The file %d was released lock by process %d\n",fd,getpid());
            break;
            
        default:
            break;
    }
}

main.c

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/file.h>
#include<stdio.h>
#include<stdlib.h>
#include"lock_set.h" 

int main()
{
    int fd;
    fd=open("hello",O_RDWR|O_CREAT);

    if(fd<0)
    {
        printf("open file hello fail!\n");
        exit(1);
    }
    lock_set(fd,F_WRLCK);
    write(fd,"Hello,world!",12);
    lock_set(fd,F_UNLCK);
    close(fd);
    return 0;
}

I/O處理的模型

  • 阻塞I/O模型:在這種模型下,若所調(diào)用的I/O函數(shù)沒有完成相關(guān)的功能,則會使進程掛起,直到相關(guān)數(shù)據(jù)到達才會返回。如常見對管道設(shè)備、終端設(shè)備和網(wǎng)絡(luò)設(shè)備進行讀寫時經(jīng)常會出現(xiàn)這種情況。
  • 非阻塞模型:在這種模型下,當(dāng)請求的I/O操作不能完成時,則不讓進程睡眠,而且立即返回。非阻塞I/O使用戶可以調(diào)用不會阻塞的I/O操作,如open()、write()和read()。如果該操作不能完成,則會立即返回出錯(例如:打不開文件)或者返回0(例如:在緩沖區(qū)中沒有數(shù)據(jù)可以讀取或者沒空間可以寫入數(shù)據(jù))。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容