5、設備和文件IO - 看這一篇就夠了

五、設備和文件IO

1、linux文件

  • 在Linux下“一切皆是文件”!不僅普通的文件,目錄、字符設備、塊設備、套接字等在unix/linux中都是以文件被對待;他們雖然類型不同,但是對其提供的卻是同一套操作界面。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tQikHuXm-1588345576629)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml8880\wps1.png)]

  • linux中的設備有2種類型:字符設備(無緩存且只能順序存取)、塊設備(有緩存且可以隨機存取)。每個字符設備和塊設備都必須有主、次設備號,主設備號相同的設備是同類設備(同一個驅動程序)。這些設備中,有些設備是對實際存在的物理硬件的抽象,而有些設備則是內核自身提供的功能(不依賴於特定的物理硬件,又稱“虛擬設備”)。每個設備在 /dev 目錄下都有一個對應的文件(節點)。
    • cat /proc/devices 查看所有的設備,分爲字符設備和塊設備顯示

2、什麼是設備文件

  • 設備文件是用來代表物理設備的。多數物理設備是用來進行輸出或輸入的,所以必須由某種機制使得內核中的設備驅動從進程中得到輸出送給設備。這可以通過打開輸出設備文件並且寫入做到,就象寫入一個普通文件。

  • 在Linux系統下,設備文件是種特殊的文件類型,其存在的主要意義是溝通用戶空間程序和內核空間驅動程序。換句話說,用戶空間的應用程序要想使用驅動程序提供的服務,需要經過設備文件來達成。Linux系統所有的設備文件都位於**/dev**目錄下。

    ls /dev -l

    stat /dev/tty 查看索引節點

  • Linux系統內部不使用文件名,而使用inode號碼來識別文件。對於系統來說,文件名只是inode號碼便於識別的別稱或者綽號。表面上,用戶通過文件名,打開文件。實際上,系統內部這個過程分成三步:

    • 系統找到這個文件名對應的inode號碼
    • 通過inode號碼,獲取inode信息
    • 根據inode信息,找到文件數據所在的block,讀出數據。

    ls -i 命令可以查看文件名對應的inode號碼

設備工作原理

Linux設備操作

3、系統調用

  • 系統調用是操作系統提供給用戶的一組“特殊”接口
  • 系統調用並非直接和程序員或系統管理員直接打交道,而是通過軟中斷的方式向內核提交請求,從而獲取內核函數的服務入口(系統調用表)
  • 系統調用讓系統從用戶空間進入內核空間內運行,運行後將結果返回給應用程序(內核態->用戶空間

函數庫調用 與 系統調用

4、C庫的文件操作

函數名 功能
fopen( ) 打開文件
fclose( ) 關閉文件
fputc( ) 將字符寫入文件中
fgetc( ) 從文件中讀取字符
fread() 將數據從文件中讀到緩衝區
fwrite() 將數據從緩衝區寫入文件
fseek( ) 在文件中搜索指定位置
fprintf( ) 操作類似於 printf(),但是用於文件
fscanf( ) 操作類似於 scanf(),但是用於文件
feof( ) 如果到達文件結尾,返回 true
ferror( ) 如果出錯,返回 true
rewind( ) 將文件位置指示器重新置於文件開頭
remove( ) 刪除文件
fflush( ) 將內部緩衝區的數據寫入指定文件

文件系統調用

  • open系統調用
  • read系統調用
  • write系統調用
  • create系統調用
  • close系統調用
  • mkdir系統調用

5、文件描述符fd

  • 每個進程PCB結構中有文件描述符指針,指向files_struct的文件描述符表,記錄每個進程打開的文件列表

  • 系統內核不允許應用程序訪問進程的文件描述符表,只返回這些結構的索引即文件描述符ID(File Description)給應用程序

  • Linux系統中,應用程序通過這些文件描述符來實現讓內核對文件的訪問

  • 每個進程能夠訪問的文件描述符是有限制的,通過#ulimit –n可以查看,默認是1024

特殊文件描述符號

  • 標準輸入STDIN_FILENO

  • 標準輸出STDOUT_FILENO

  • 標準錯誤STDERR_FILENO

每個進程被加載後,默認打開0,1,2這三個文件描述符

6、open系統調用

  • 有幾種方法可以獲得允許訪問文件的文件描述符。最常用的是使用open()(打開)系統調用

  • 函數原型

	int open(const char *path, int flags);
	int open(const char *path, int flags, mode_t mode);
  • 參數

    • path:文件的名稱,可以包含(絕對和相對)路徑
    • lags:文件打開模式
    • mode:用來規定對該文件的所有者,文件的用戶組及系統中其他用戶的訪問權限
  • 返回值

    • 打開成功,返回文件描述符;
      - 打開失敗,返回-1

打開文件的方式

打開方式 描述
O_RDONLY 打開一個供讀取的文件
O_WRONLY 打開一個供寫入的文件
O_RDWR 打開一個可供讀寫的文件
O_APPEND 寫入的所有數據將被追加到文件的末尾
O_CREAT 打開文件,如果文件不存在則建立文件
O_EXCL 如果已經置O_CREAT且文件存在,則強制open()失敗
O_TRUNC 在open()時,將文件的內容清空

所有這些標誌值的符號名稱可以通過#include<fcntl.h>訪問

訪問權限

打開方式 描述
S_IRUSR 文件所有者的讀權限位
S_IWUSR 文件所有者的寫權限位
S_IXUSR 文件所有者的執行權限位
S_IRWXU S_IRUSR|S_IWUSR|S_IXUSR
S_IRGRP 文件用戶組的讀權限位
S_IWGRP 文件用戶組的寫權限位
S_IXGRP 文件用戶組的執行權限位
S_IRWXG S_IRGRP|S_IWGRP|S_IXGRP
S_IROTH 文件其他用戶的讀權限位
S_IWOTH 文件其他用戶的寫權限位
S_IXOTH 文件其他用戶的執行權限位
S_IRWXO S_IROTH|S_IWOTH|S_IXOTH

文件打開示例

#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
    int outfd = 0;
    outfd = open("myfile",O_WRONLY | O_CREAT | O_TRUNC,S_IRWXU | S_IRGRP);
    if(outfd == -1){
        perror("fail to open file\n");
        exit(-1);
    }else{
        perror("success to open file\n");
    }
    close(outfd);
    return 0;
}

7、close系統調用

  • 將進程中fd對應的文件描述表結構釋放

  • 函數原型:

int close(int fd);
  • 函數參數:
    • fd :要關閉的文件的文件描述符
  • 返回值
    • 如果出現錯誤,返回-1
    • 調用成功返回0

8、read系統調用

  • 一旦有了與一個打開文件描述相連的文件描述符,只要該文件是用O_RDONLY或O_RDWR標誌打開的,就可以用read()系統調用從該文件中讀取字節

  • 函數原型:

int read(int fd, void *buf, size_t nbytes)
  • 參數
    • fd:想要讀的文件的文件描述符
    • buf: 指向內存塊的指針,從文件中讀取來的字節放到這個內存塊中
    • nbytes: 從該文件複製到buf中的字節個數
  • 返回值
    • 如果出現錯誤,返回 -1
    • 返回從該文件複製到規定的緩衝區中的字節數

9、write系統調用

  • 用write()系統調用將數據寫到一個文件中

  • 函數原型:

int write(int fd,void *buf,size_t nbytes);
  • 函數參數:
    • fd :要寫入的文件的文件描述符
    • buf: 指向內存塊的指針,從這個內存塊中讀取數據寫入到文件中
    • nbytes: 要寫入文件的字節個數
  • 返回值
    • 如果出現錯誤,返回 -1
    • 如果寫入成功,則返回寫入到文件中的字節個數
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
    int fd = 0,size = 0;
    char buf[] = "Hello world!";
    fd = open("first",O_WRONLY | O_TRUNC | O_CREAT,S_IRWXU);
    if(fd > 0){
        size = write(fd,buf,sizeof(buf));
        if(size > 0){
            printf("write data to file success\n");
        }
        close(fd);
    }
    
    return 0;
}

10、文件的隨機讀寫

  • 到目前爲止的所有文件訪問都是順序訪問。這是因爲所有的讀和寫都從當前文件的偏移位置開始,然後文件偏移值自動地增加到剛好超出讀或寫結束時的位置,使它爲下一次訪問作好準備。
  • 有個文件偏移這樣的機制,在Linux系統中,隨機訪問就變得很簡單,你所需做的只是將當前文件移值改變到有關的位置,它將迫使一次read()或write()發生在這一位置。(除非文件被O_APPEND打開,在這種情況下,任何write調用仍將發生在文件結束處)

11、lseek系統調用

  • 功能說明:通過指定相對於開始位置、當前位置或末尾位置的字節數來重定位 curp,這取決於 lseek() 函數中指定的位置
  • 原型:
off_t lseek(int fd,off_t offset,int whence);
  • 參數
    • fd:文件描述符
    • offset:偏移量
    • whence:搜索的起始位置
  • 返回值
    • 返回新的文件偏移值
whence 文件位置
SEEK_SET 從文件開始處計算偏移
SEEK_CUR 從當前文件的偏移值計算偏移
SEEK_END 從文件的結束處計算偏移

12、chmod和fchmod系統調用

  • 功能說明:用來改變給定路徑名pathname的文件的權限位

  • 原型:

int chmod(char *path,mode_t mode);
int fchmod(int fd,mode_t mode);
  • 返回值:調用成功返回0,失敗返回-1

13、chown和fchown系統調用

  • 功能說明:用來改變文件所有者的識別號(owner id)或者它的用戶組識別號(group ID)

  • 原型:

int chown(char *path, uid_t owner,gid_t group);
int fchown(int fd, uid_t owner,gid_t group);
  • 參數
    • owner:所有者識別號
    • group:用戶組識別號

14、mkdir系統調用

  • 功能說明:用來創建一個稱爲pathname的新目錄,它的權限位設置爲mode

  • 原型:

int  mkdir(char *pathname,mode_t mode);
  • 返回值:調用成功返回0,失敗返回-1

15、rmdir系統調用

  • 功能說明:刪除一個空目錄

  • 原型:

int  rmdir(char *pathname);
  • 返回值:調用成功返回0,失敗返回-1

16、目錄訪問

(1)opendir

  • 功能說明:打開一個目錄

  • 原型:

DIR*  opendir(char *pathname);
  • 返回值
    • 打開成功,返回一個目錄指針
    • 打開失敗,則返回 0

(2)readdir

  • 功能說明:訪問指定目錄中下一個連接的細節
  • 原型:
struct  dirent*  readdir(DIR *dirptr);
  • 返回值:
    • 返回一個指向dirent結構的指針,它包含指定目錄中下一個連接的細節;
    • 沒有更多連接時,返回NULL
struct dirent
{
	long d_ino;                 /* 目錄i結點編號 */
	off_t d_off;                /* 目錄文件開關至此目錄進入點的位移 */
	unsigned short d_reclen;    /* d_name的長度 */
	char d_name [NAME_MAX+1];   /* 以NULL結尾的文件名 */
}
  • 如果調用opendir打開某個目錄之後,第一次調用readdir函數,則返回的是該目錄下第一個文件的信息,第二次調用readdir函數返回該目錄下第二個文件的信息,依此類推。如果該目錄下已經沒有文件信息可供讀取,則返回NULL。

(3)closedir

  • 功能說明:關閉一個已經打開的目錄
  • 原型:
int closedir (DIR  *dirptr);

(4)示例:目錄操作

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

int my_read_dir(const char *path){
	DIR * dir;
 	struct dirent *ptr;
	if((dir = opendir(path)) == NULL){
		return -1;
	}
    while((ptr = readdir(dir)) != NULL){
        printf("file name: %s\n",ptr->d_name);
    }
    closedir(dir);
    return 0;
}
int main(int argc,char *argv[]){
    if(my_read_dir(argv[1]) < 0 ){
        exit(1);
    }
    return 0;
}

17、文件記錄鎖 - fcntl函數

  • 什麼是文件鎖:當有多個進程同時對某一文件進行操作時,就有可能發生數據的不同步,從而引起錯誤,該文件的最後狀態取決於寫該文件的最後一個程序。
  • Linux中文件記錄鎖可以對文件某一區域進行文件記錄鎖的控制。它是通過fcntl函數來實現的。

fcntl函數

  • 功能說明:管理文件記錄鎖的操作
  • 原型:
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd,int cmd,struct flck *lock);
  • 參數:
    • fd:文件描述符
    • cmd:功能符號
      • F_SETLK:用來設置或釋放鎖
      • F_GETLK:用來獲得鎖信息
    • lock:存儲鎖信息的結構體指針
  • 返回值
    • 調用成功返回 0,失敗返回 -1
//鎖信息結構體

struct flock
{
    short l_type;	//鎖的類型
    short l_whence;	//偏移量的起始位置
    off_t l_start;	//從l_whence的偏移量
    off_t l_len;	//從l_start開始的字節數
    pid_t l_pid;	//鎖所屬進程ID(一般不用)
}

l_type:
	F_RDLCK讀鎖、F_WRLCK寫鎖、F_UNLCK空鎖
l_whence:
	SEEK_SET起始、SEEK_CUR當前、SEEK_END末尾
l_len:0時表示從起點開始直至最大可能位置爲止

示例

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

int main(){
    int fd;
    struct flock lock;
    fd = open("example",O_CREAT | O_TRUNC | O_RDWR,S_IRWXU);
    if(fd == -1){
        printf("open file error\n");
    }
    memset(&lock,0,sizeof(struct flock));
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;
    if(fcntl(fd,F_GETLK,&lock) == 0){
        if(lock.l_type != F_UNLCK){//如果有鎖,不能上鎖
            printf("lock can not by set in fd\n");
        }else{//上鎖
            lock.l_type = F_WRLCK;
            if(fcntl(fd,F_SETLK,&lock) == 0){
                printf("set write lock success!\n");
            }else{
                printf("set write lock fail!\n");
            }
            getchar();
            //解鎖
            lock.l_type = F_UNLCK;
            fcntl(fd,F_SETLK,&lock);
        }
    }
    close(fd);
    return 0;
}

專欄 《linux網絡編程》 將持續更新中…
從linux 零基礎 到 高併發服務器架構

如果我的文章能夠幫到您,可以點個贊!
您的每次 點贊、關注、收藏 都是對我最大的鼓勵!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章