Linux操作系統-文件(2)

Linux操作系統—文件(2)(2015-8-17)

分類:Linux操作系統

二:底層文件訪問

文件描述符

  文件描述符是一個非負整數。對內核而言,所有打開的文件都由文件描述符引用。
  當打開一個現存文件或者創建一個新的文件時,內核向進程返回一個文件描述符。當讀,寫一個文件時,用open或者create返回的文件描述符標識該文件,將其作爲參數傳遞給read或者write。從內核源碼的角度來看,文件描述符其實是當前進程所打開的文件結構數組的下標。
  按照慣例,UNIX/Linux Shell使文件描述符0與進程的標準輸入相結合,文件描述符1與標準輸出相結合,文件描述符2與標準出錯相結合。在頭文件< unistd.h> 中定義了常量STDIN_FILENO,STDOUT_FILENO和STDERR_FILENO,其值分別爲0,1,2。
  一個進程能打開的文件數由< limits.h >文件中的OPEN_MAX限定。

文件的創建,打開

  調用open函數可以打開或創建一個文件,其原型如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

疑惑:這是什麼?函數重載麼?可函數重載不是C++裏面的東西嗎?

  打開一個文件後,即在文件描述符和文件之間建立了一個關聯。open函數既可以打開一個已近存在的文件,也可以創建一個新的文件。具體執行打開操作還是創建操作由flag參數指定。open函數各參數和返回值的含義如下:
1. pathname:要打開或創建的文件的名字
2. flags:由下列一個或多個常數進行或運算構成

- O_RDONLY:只讀打開
- O_WRONLY:只寫打開
- O_RDWR:讀,寫打開
- APPEND:每次寫時追加到文件的尾端
- O_CREAT:若此文件不存在則創建它,應結合第三個參數mode使用
- O_EXCL:結合O_CREATE,當文件不存在時,才創建文件
- O_TRUNC:如果此文件存在,而且爲只讀或只寫則將其長度截斷爲0
  1. mode:存取許可權位,一個32位無符號整數,僅當創建新文件時才使用,由下列一個或多個常數進行或運算構成。應注意,最終文件權限受系統變量umask限制,是所設權限和umask的二進制“非”進行二進制“與”所得的結果。

    • S_IRUSR:文件所有者讀
    • S_IWUSR:文件所有者寫
    • S_IXUSR:文件所有者執行
    • S_IRGRP:用戶組讀
    • S_IWGRP:用戶組寫
    • S_IXGRP:用戶組執行
    • S_IROTH:其它用戶讀
    • S_IWOTH:其它用戶寫
    • S_IXOTH:其它用戶執行
  2. 返回值

    • 成功時返回一個文件描述符
    • 失敗時返回-1,並設置全局變量errno指明失敗原因

文件的關閉

  使用open函數打開的文件在操作結束後,應當使用close函數關閉。close函數的原型如下。

#include <unistd.h>
int close(int filedes);

  調用close函數後,終止了文件描述符與文件之間的關聯,被關閉的文件描述符重新變爲可用。關閉一個文件的同時也釋放了該進程加在該文件上的所有記錄鎖。當一個進程終止時,它所打開的所有文件都由內核自動關閉。
  close函數的參數和返回值的含義如下:
1. filedes爲待關閉的文件描述符
2. 返回值:成功時返回0,失敗時返回-1


文件的讀,寫

  在Linux底層,可使用read函數讀取已打開文件中的數據,用write函數寫新的數據到已打開的文件中。
  write函數的原型:

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

  各參數和返回值的含義如下:
- fd:文件描述符
- buf:待寫入文件的數據的緩衝區, 是一個常量指針
- count:待寫的字節數,該字節數應當小於或等於緩衝區大小
- 返回值:若成功,則返回已寫的字節數,若出錯,則返回-1,錯誤值記錄在errno中

比如:

#include <unistd.h>
#include <stdlib.h>

int main()
{
    if ((write(1, "Here is some data\n", 18)) != 18)
        write(2, "A write error has occurred on file descriptor 1\n", 46);

    return 0;
}

解釋:第六行,write函數向文件描述符1(也就是標準輸出——屏幕)寫入18字節的數據,如果出錯了,也即是返回值不爲18,則向文件描述符2(也就是標準錯誤輸出)寫入46字節的數據。程序的運行結果如下:

biantiao@lazybone1994-ThinkPad-E430:~/sh$ gcc -o ex_write ex_write.c
biantiao@lazybone1994-ThinkPad-E430:~/sh$ ./ex_write
Here is some data
biantiao@lazybone1994-ThinkPad-E430:~/sh$ 

  調用read函數可以從已打開的文件中讀取數據,其原型如下:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

  read函數各參數和返回值含義如下:
- buf:用於放置讀取到的數據的緩衝區
- 返回值:若成功,返回已讀取的字節數(0表示已到達文件尾),若出錯,返回-1,錯誤記錄在errno中。

比如:

#include <unistd.h>
#include <stdlib.h>

int main()
{
    char buffer[128];
    int nread;

    nread = read(0, buffer, 128);
    if (nread == -1){
        write(2, "A read error has occurred.\n", 26);
    }
    else if ((write(1, buffer, nread)) != nread){
        write(2, "A write error has occurred.\n", 27);
    }

    return 0;
}

解釋:第9行調用read函數,從鍵盤讀取最多128個字節(文件描述符爲1),read返回實際讀取到的字節數記錄在nread中。第10行爲判斷是否出錯。第12行調用write函數,將讀取到的buffer中的數據重新輸出到標準輸出設備。如果輸出的字節數與讀取到的字節數不一致,則在第13行向標準錯誤輸出設備輸出錯誤提示。

biantiao@lazybone1994-ThinkPad-E430:~/sh$ gcc -o ex_read ex_read.c
biantiao@lazybone1994-ThinkPad-E430:~/sh$ ./ex_read
This is a sample example for read function
This is a sample example for read function
biantiao@lazybone1994-ThinkPad-E430:~/sh$

文件的定位

  文件的定位。先來說一些乾貨,算是普及知識。
  對於可隨機訪問的文件,如磁盤文件,人們往往希望能夠按需定位到文件的某個位置進行讀,寫操作。這可以通過調用lseek函數來完成。
  實際上,每個已打開的文件都有一個與其相關聯的“當前文件位移量”。通常,讀,寫操作都從當前文件位移量處開始,並在讀,寫完成後使位移量增加所讀寫的字節數。
  當打開一個文件時,如果指定O_APPEND選項,該位移量被設置爲文件的長度,否則該位移量被設置爲0。如果要隨機得訪問文件的內容,可調用lseek函數顯示地定位一個已打開文件的“當前文件位移量”
  lseek函數的原型爲:

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);

  lseek僅將當前的文件位移量記錄在內核變量內,並不引起任何I/O操作。lseek函數各參數和返回值的含義如下:
- fd:文件描述符
- offset:位移量。off_t類型一般爲“long int”的typedef
- whence:指定位移量相對於何處開始,可取下面三個值

- SEEK_SET:文件開始的位置
- SEEK_CUR:文件讀寫指針當前位置
- SEEK_END:文件結束位置
  • 返回值:若成功爲當前讀寫位置相對於頭文件的位移量;若出錯爲-1,錯誤值記錄在errno中

  下面是一個綜合實例:

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

char buf1[] = "abcdefghij";
char buf2[] = "ABCDEFGHIJ";

int main()
{
    int fd;

    /* 調用open函數以只寫(O_WRONLY)和創建(O_CREAT)方式在當前目錄創建一個所有者具有
       讀(S_IRUSR)和寫(S_IWUSR)權限的普通文件file.hole,打開的文件描述符記錄於fd變量
       如果open的返回值小於0,說明打開文件失敗,輸出錯誤提示並退出程序
    */
    if ( (fd = open("file.hole", O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)) < 0 ){
        write(2, "create error.\n", 13);
        return -1;
    }

    /* 向文件寫入“abcdefghij”,如果寫入成功,則當前位移量爲10 */
    if (write(fd, buf1, 10) != 10){
        write(2, "buf1 write error.\n", 17);
        return -1;
    }

    /* 當前的位移量爲10,調用lseek函數,將當前位移量設置爲40 */
    if (lseek(fd, 40, SEEK_SET) == -1){
        write(2, "lseek error.\n", 12);
        return -1;
    }

    /* 從當前位移量爲40處開始寫入“ABCDEFGHIJ” */
    if (write(fd, buf2, 10) != 10){
        write(2, "buf2 write error.\n", 17);
        return -1;
    }

    /* 寫入成功後,當前位移量爲50 */

    return 0;
}

  將該程序編譯並運行結果如下:

biantiao@lazybone1994-ThinkPad-E430:~/sh/file$ gcc -o ex_lseek ex_lseek.c
biantiao@lazybone1994-ThinkPad-E430:~/sh/file$ ./ex_lseek
biantiao@lazybone1994-ThinkPad-E430:~/sh/file$ od -c file.hole
0000000   a   b   c   d   e   f   g   h   i   j  \0  \0  \0  \0  \0  \0
0000020  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
0000040  \0  \0  \0  \0  \0  \0  \0  \0   A   B   C   D   E   F   G   H
0000060   I   J
0000062
biantiao@lazybone1994-ThinkPad-E430:~/sh/file$

說明:od -c命令表示以字符形式顯示二進制文件的內容。


讀取文件的屬性

  Linux提供了stat系列函數用來讀取文件的屬性信息。這些函數的原型如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *file_name, struct stat *buf);
int fstat(int filedes, struct stat *buf);
int lstat(const char *file_name, struct stat *buf);

  這些函數各參數和返回值的含義如下:
- file_name:文件名
- filedes:文件描述符
- buf:文件信息結構緩衝區,該緩衝區爲一個結構體,定義如下:

struct stat{
    dev_t       st_dev;         /* 保存本文件的設備的ID */
    ino_t       st_ino;         /* 與文件關聯的索引節點號 */
    mode_t      st_mode;        /* 文件權限和文件類型信息 */
    nlink_t     st_nlink;       /* 該文件上硬鏈接的個數 */
    uid_t       st_uid;         /* 文件所有者的UID號 */
    gid_t       st_gid;         /* 文件所有者的GID號 */
    dev_t       st_rdev;        /* 特殊文件的設備ID */
    off_t       st_size;        /* 文件大小 */
    blksize_t   st_blksize;     /* 文件系統I/O的塊大小 */
    blkcnt_t    st_blocks;      /* 塊數 */
    time_t      st_atime;       /* 最後訪問時間 */
    time_t      st_mtime;       /* 最後修改時間 */
    time_t      st_ctime;       /* 最後狀態改變時間 */
};
  • 返回值:成功爲0,若出錯爲-1,錯誤值記錄在errno中

說明:stat和lstat函數的區別:當文件是一個符號鏈接時,lstat返回的是該符號鏈接本身的信息,而stat返回的是該鏈接指向的文件的信息。調用stat系列函數時,文件的屬性信息均保存在struct stat的結構體類型的buf當中。

  看看一看st_mode成員。st_mode成員的每一個位代表了一種權限或文件類型,可以將該成員與下表中所示的標誌位進行二進制“與”運算以測試文件權限或類型。

標誌位 常量值 含義
S_IFMT 0170000 文件類型掩碼
S_IFSOCK 0140000 套接字
S_IFLNK 0120000 符號鏈接
S_IFREG 0100000 普通文件
S_IFBLK 0060000 塊設備
S_IFDIR 0040000 目錄
S_IFCHR 0020000 字符設備
S_IFIFO 0010000 FIFO(命名管道)
S_ISUID 0004000 設置了SUID
S_ISGID 0002000 設置了SGID
S_ISVTX 0001000 設置了粘滯位
S_IRWXU 00700 文件所有者權限掩碼
S_IRUSR 00400 文件所有者可讀
S_IWUSR 00200 文件所有者可寫
S_IXUSR 00100 文件所有者可執行
S_IRWXG 00070 文件所屬組權限掩碼
S_IRGRP 00040 文件所屬組可讀
S_IWGRP 00020 文件所屬組可寫
S_IXGRP 00010 文件所屬組可執行
S_IRWXO 00007 其他用戶權限掩碼
S_IROTH 00004 其他用戶可讀
S_IWOTH 00002 其他用戶可寫
S_IXOTH 00001 其他用戶可執行

  除了直接使用二進制與的方法進行文件類型測試外,Linux還提供了以下宏用於文件類型的判定(其中m參數即爲st_mode成員)
- S_ISREG(m):是否爲普通文件
- S_ISDIR(m):是否爲目錄
- S_ISCHR(m):是否爲字符設備
- S_ISBLK(m):是否爲塊設備
- S_ISFIFO(m):是否爲管道設備
- S_ISLNK(m):是否爲符號連接
- S_ISSOCK(m):是否爲套接字

實例:下面這個實例演示瞭如何使用stat函數

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

int main(int argc, char *argv[])
{
    struct stat sb;

    if (argc != 2){
        printf("Usage : %s <pathname>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    if (stat(argv[1], &sb) == -1){

        perror("stat");
        exit(EXIT_FAILURE);
    }

    printf("File type: ");
    switch (sb.st_mode & S_IFMT){
        case S_IFBLK : printf(" Block device.\n");          break;
        case S_IFCHR : printf(" Character device\n");       break;
        case S_IFDIR : printf(" Directory\n");              break;
        case S_IFIFO : printf(" FIFO/Pipe\n");              break;
        case S_IFLNK : printf(" Symlink\n");                    break;
        case S_IFREG : printf(" Regular file\n");           break;
        case S_IFSOCK : printf("Socket\n");                 break;
        default :      printf("Unknown file\n");            break;      
    }
    printf("I-node number : %ld\n", (long)sb.st_ino);
    printf("Mode : %lo (octal)\n", (unsigned long)sb.st_mode);
    printf("Link count : %ld\n", (long)sb.st_nlink);
    printf("Ownership : UID=%ld GID=%ld\n", (long)sb.st_uid, (long)sb.st_gid);
    printf("Preferred I/O block size : %ld bytes\n", (long)sb.st_blksize);
    printf("Blocks allocated : %lld\n",(long long)sb.st_blocks);

    printf("Last status change : %s", ctime(&sb.st_ctime));
    printf("Last file access : %s", ctime(&sb.st_atime));
    printf("Last file modification : %s", ctime(&sb.st_mtime));

    return 0;
}

  編譯並運行

biantiao@lazybone1994-ThinkPad-E430:~/桌面$ gcc -o stat stat.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ./stat stat.c
File type:  Regular file
I-node number : 4476379
Mode : 100664 (octal)
Link count : 1
Ownership : UID=1001 GID=1001
Preferred I/O block size : 4096 bytes
Blocks allocated : 8
Last status change : Thu Aug 20 09:09:17 2015
Last file access : Thu Aug 20 09:09:18 2015
Last file modification : Thu Aug 20 09:09:17 2015
biantiao@lazybone1994-ThinkPad-E430:~/桌面$

  怎樣在讀取一個文件屬性之前判斷該文件是否存在呢?答案是使用access函數。
  access函數進行文件的存取許可測試,它的原型如下:

#include <unistd.h>
int access(const char *pathname, int mode);

  access函數按實際用戶的ID和實際組ID進行存取測試,其各參數和返回值的含義如下:

1. pathname : 文件名
2. mode : 測試項,其值可以是以下之一個或多個值按位或的結果

- R_OK:測試讀許可權
- W_OK:測試寫許可權
- X_OK:測試執行許可權
- F_OK:測試文件是否存在

3. 返回值:成功返回0,失敗返回-1

  下面是一個使用access函數的例子,功能是測試提供的文件是否存在並可讀,如果不存在或不可讀則輸出相應的提示信息,如果可讀則讀取前20個字節的數據並輸出:

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

int main(int argc, char *argv[])
{
    int i, num;
    int fd;
    char buf[20];

    if (argc != 2){
        printf("Usage : %s <pathname>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    if (access(argv[1], F_OK) != 0){
        printf("The file '%s' doesn't existed!\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    if (access(argv[1], R_OK) != 0){
        printf("The file '%s' can not be read!\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    if ((fd = open(argv[1], O_RDONLY)) < 0){
        printf("Failed to open file '%s' for read!\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    if ((num = read(fd, buf, 20)) < 0){
        close(fd);
        printf("Failed to read file '%s'!\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    printf("The starting %d bytes of '%s' is :\n", num, argv[1]);
    for (i = 0; i < num; i++){
        printf("%c", buf[i]);
    }
    printf("\n");
    close(fd);

    return 0;
}

  編譯並運行

biantiao@lazybone1994-ThinkPad-E430:~/桌面$ gcc -o access access.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ./access
Usage : ./access <pathname>
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ./access access.c
The starting 20 bytes of 'access.c' is :
#include <sys/types.
biantiao@lazybone1994-ThinkPad-E430:~/桌面$
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章