【Linux 應用編程】文件IO操做 - 經常使用函數

Linux 系統中的各類輸入輸出,設計爲「一切皆文件」。各類各樣的IO統一用文件形式訪問。node

文件類型及基本操做

Linux 系統的大部分系統資源都以文件形式提供給用戶讀寫。這些文件能夠分爲:linux

  • 普通文件:即通常意義上的磁盤文件;
  • 設備文件:系統中的具體設備;
  • 管道文件、FIFO 文件:用於進程間通訊;
  • 套接字(socket)文件:用於網絡通訊方面。

文件的通用操做爲:打開、關閉、讀、寫、建立。對應 Linux 系統的 API 接口函數分別爲 open()close()read()write()create()。這些函數經過文件描述符 File Descriptor 實現 IO 操做。web

文件描述符和文件描述符表

在進程中,經過 open 函數打開文件或經過 create 函數建立文件後,會返回一個整數,這個整數就是表明這個文件的文件描述符。經過 ulimit -n 命令能夠查看每一個進程最大支持同時打開多少文件。緩存

操做系統在進程控制塊(PCB,Process Control Block)中,幫每一個進程維護了一個文件描述符表,進程打開的全部文件,都會在這個表裏登記。打開文件獲得的整型返回值,實際上就是指向表裏某條記錄的索引。後續執行讀寫操做時,經過傳入的文件描述符,在文件描述符表進行查找,從而定位到文件的具體位置。bash

3個特殊的文件描述符

Linux 系統在啓動時,標準 IO 會佔用掉前 3 個文件描述符的位置:網絡

  • 標準輸入 stdin 的文件描述符是 0
  • 標準輸出 stdout 的文件描述符是 1
  • 標準錯誤stderr 的文件描述符是 2。

文件 I/O 經常使用頭文件

部分函數須要同時引入多個頭文件,是由於這些函數中用到的常量定義,跟函數定義不在同一個頭文件裏。socket

#include <sys/types.h> /* 定義數據類型,如 ssize_t,off_t 等 */
#include <fcntl.h> /* 定義 open,creat 等函數原型,建立文件權限的符號常量 S_IRUSR 等 */
#include <unistd.h> /* 定義 read,write,close,lseek 等函數原型 */
#include <errno.h> /* 與全局變量 errno 相關的定義 */
#include <sys/ioctl.h> /* 定義 ioctl 函數原型 */

open 和 creat 函數

函數頭文件:async

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

函數原型:svg

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
// 若是 flags 包含 O_CREAT,則相對於 creat 函數,必須指定 mode 參數
int creat(const char *pathname, mode_t mode);

參數:函數

  • pathname:文件名稱
  • flags:標誌,包含三種:
    • 訪問方式標誌:必須包含。只讀、只寫或讀寫三種中的一個:
      • O_RDONLY:只讀
      • O_WRONLY:只寫
      • O_RDWR:讀寫
    • 文件建立標誌:能夠包含。
      • O_CLOEXEC
      • O_CREAT:若是文件不存在,則建立文件。文件的 UID 會被設置爲進程的 UID,GID 會被設置爲進程或父進程的 GID。此時必須指定第三個參數 mode。
      • O_DIRECTORY
      • O_EXCL:跟 O_CREAT 一同使用,確保必定會建立文件。若是文件已經存在則返回 -1。
      • O_NOCTTY
      • O_NOFOLLOW
      • O_TRUNC:文件截斷。
      • O_TTY_INIT
    • 文件狀態標誌:能夠包含。該標誌能夠經過 fcntl 函數從新檢索 retrieve 和修改。
      • O_APPEND:追加模式。每次 write 或 lseek 函數執行前,指針會偏移到文件末尾。NFS 文件系統可能會有異常。
      • O_ASYNC
      • O_DIRECT
      • O_LARGEFILE
      • O_NOATIME
      • O_NONBLOCK or O_NDELAY:非阻塞。
      • O_PATH
      • O_SYNC
  • mode:建立文件時,設置文件權限。能夠直接使用 0777 之類的數字,也能夠用下面的常量:
    • S_IRWXU 00700 user (file owner) has read, write and execute permission
    • S_IRUSR 00400 user has read permission
    • S_IWUSR 00200 user has write permission
    • S_IXUSR 00100 user has execute permission
    • S_IRWXG 00070 group has read, write and execute permission
    • S_IRGRP 00040 group has read permission
    • S_IWGRP 00020 group has write permission
    • S_IXGRP 00010 group has execute permission
    • S_IRWXO 00007 others have read, write and execute permission
    • S_IROTH 00004 others have read permission
    • S_IWOTH 00002 others have write permission
    • S_IXOTH 00001 others have execute permission

返回值:報錯時返回 -1,不然返回文件描述符。

示例:

#include <fcntl.h>
#include <stdio.h>

int main()
{
    int fd;
    char name[] = "666.txt";
    // 若是文件不存在,就建立文件,權限爲全部者RWX,組和其餘人無權限
    fd = open(name, O_RDONLY | O_CREAT, S_IRWXU);
    // fd 小於 0 表示出錯,須要處理
    if (fd < 0) //...
    printf("%d\n", fd);
    close(fd);
	
	// 若是文件已經存在,會把文件內容清空。權限爲用戶RW,組用戶W,其餘人R,即 0x321
	fd = cerat(name, S_IRUSR | S_IWUSR | S_IWGRP | S_IROTH);
    return 0;
}

close 函數

Linux 系統中,文件能夠屢次打開,例如多個進程同時打開一個文件,一個進程反覆屢次打開。內核記錄了文件的打開次數,只要還有進程沒關閉文件,就不會關閉文件。

close(fd);

read 函數

頭文件:

#include <unistd.h>

函數原型:

ssize_t read(int fd, void *buf, size_t count);

read 函數會嘗試從文件描述符 fd 中讀取 count 個字節到緩衝區 buf 中。

返回值:
成功時返回讀到的字節數,0表示讀到文件末尾了。若是返回字節數小於指定的字節數,不必定出錯,有可能文件就剩這麼多數據了。出錯時返回 -1,並設置 errno 爲合適值。
示例:

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>

int main()
{
    int fd, res;
    char buf[20];
    char name[] = "666.txt";

    fd = open(name, O_RDONLY);
    res = read(fd, buf, sizeof(buf));
    printf("%s\n", buf);
    return 0;
}

write 函數

頭文件:

#include <unistd.h>

函數原型:

ssize_t write(int fd, const void *buf, size_t count);

write 函數會嘗試從緩衝區 buf 中讀取 count 個字節,寫到文件描述符 fd 中。

返回值:
成功時返回寫入的字節數。若是返回字節數小於指定的字節數,不必定出錯,有多是磁盤滿了。出錯時返回 -1,並設置 errno 爲合適值。
示例:

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>

int main()
{
	int fd, res;
	char str[] = "hello world!";
	char name[] = "666.txt";
	fd = open(name, O_WRONLY); // 必需要有寫權限
	res = write(fd, str, sizeof(str));
	printf("%d\n", res);
	return 0;
}

fsync 函數

磁盤讀寫速度很慢,爲了優化性能,Linux 在寫磁盤時,加了一層緩存,數據攢夠必定數量或程序結束後纔將數據寫入磁盤。write 函數每次只是將數據寫到緩存,若是須要強制其寫入磁盤,須要使用 fsync 命令。

頭文件:

#include <unistd.h>

函數原型:

int fsync(int fd);
int fdatasync(int fd);

返回值:
操做成功返回 0,不然返回 -1,同時設置全局變量 errno。

sync() 函數同步整個系統修改過的緩存數據,而 fsync() 則只針對一個具體文件。

lseek 函數

有的設備支持隨機讀寫文件,例如磁盤,而有的則只支持順序讀寫,例如管道、套接字和 FIFO。支持隨機讀寫的設備,能夠經過 lseek 函數移動讀寫位置。以後的讀寫操做,將會從這個新位置開始。

頭文件:

#include <unistd.h>

函數原型:

off_t lseek(int fd, off_t offset, int whence);

參數:

  • offset:目標位置,其偏移的參照點,由第三個參數 whence 決定
  • whence:有效值是 SEEK_SET、SEEK_CUR、SEEK_END,含義以下:
    • SEEK_SET 設置新的讀寫位置爲從文件開頭算起,偏移 offset 字節;
    • SEEK_CUR 設置新的讀寫位置爲從當前所在的位置算起,偏移 offset 字節,正值表示往文件尾部偏移,負值表示往文件頭部偏移;
    • SEEK_END 設置新的讀寫位置爲從文件結尾算起,偏移 offset 字節,正值表示往文件尾部偏移,負值表示往文件頭部偏移。

返回值:
操做成功則返回新的讀寫位置,不然返回 -1。按順序讀寫的文件不支持 lseek 操做,對這類文件調用 lseek(),將返回-1,且 errno=ESPIPE。

若是隻是想測試設備是否支持該操做,能夠執行這個語句,只有返回值大於 -1,就是支持的:

res = lseek(fd, 0, SEEK_CUR);

示例:

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main()
{
	int fd, res;
	char name[] = "666.txt";
	char buf[20];
	fd = open(name, O_RDONLY);
	lseek(fd, 5, SEEK_SET);
	res = read(fd, buf, 10);
	printf("%s\n", buf);
	return 0;
}

ioctl 函數

ioctl 是文件 IO 的雜項函數,能夠實現一些設備相關的操做,例如修改寄存器的值。

頭文件:

#include <sys/ioctl.h>

函數原型:

int ioctl(int d, int request, ...);

參數:

  • d:打開文件的描述符
  • request:文件的操做命令,參數值決定後面的參數含義,... 表示從參數是可選的、類型不肯定的。不一樣的文件,cmd 通常不一樣,好比嵌入式系統中的設備文件,蜂鳴器(BUZZER)和模數轉換(ADC)。

返回值:
操做成功則返回0,不然返回 -1。部分設備可能返回正數表示參數。

stat 和 lstat 函數

跟 Linux 終端中的 stat 命令做用同樣,stat 函數也用來查看文件屬性。

# stat tmux-client-14353.log 
  File: ‘tmux-client-14353.log’
  Size: 54        	Blocks: 8          IO Block: 4096   regular file
Device: fd01h/64769d	Inode: 256424      Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2018-11-17 14:56:09.963358724 +0800
Modify: 2018-11-17 14:56:16.992381417 +0800
Change: 2018-11-17 14:56:16.992381417 +0800
 Birth: -

頭文件及函數原型:

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

int stat(const char *path, struct stat *buf); /* 查看 path 文件名指向的文件的屬性,放到 buf 中*/
int fstat(int fd, struct stat *buf); /* 文件名變成文件描述符 */
int lstat(const char *path, struct stat *buf); /* 文件名是一個符號連接,查看這個符號連接的屬性 */

返回值:

struct stat {
    dev_t     st_dev;     /* 文件的設備編號,ID of device containing file */
    ino_t     st_ino;     /* Inode 編號,inode number */
    mode_t    st_mode;    /* 文件類型和權限,protection */
    nlink_t   st_nlink;   /* 硬連接個數,number of hard links */
    uid_t     st_uid;     /* 用戶ID,user ID of owner */
    gid_t     st_gid;     /* 組ID,group ID of owner */
    dev_t     st_rdev;    /* device ID (if special file) */
    off_t     st_size;    /* 文件大小,total size, in bytes */
    blksize_t st_blksize; /* 塊大小,blocksize for file system I/O */
    blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
    time_t    st_atime;   /* time of last access */
    time_t    st_mtime;   /* 最後一次修改時間,time of last modification */
    time_t    st_ctime;   /* time of last status change */
};

示例:

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

int main()
{
    char name[] = "666.txt";
    struct stat buf;
    int ret = stat(name, &buf);
    if (ret < 0)
    {
        printf("get file status error, errorno is:%d ", errno);
    }
    printf("UID is: %d\nGID is: %d\nsize is: %d\n", (int)buf.st_uid, (int)buf.st_gid, (int)buf.st_size);
    return 0;
}

access 函數

檢查當前用戶對文件是否具備某個權限,還能夠判斷文件是否存在。

頭文件及函數原型:

#include <unistd.h>

int access(const char *pathname, int mode);

參數:

  • mode 支持4個參數:
    • R_OK:是否有讀權限
    • W_OK:是否有寫權限
    • X_OK:是否有執行權限
    • F_OK:判斷文件是否存在

示例:

#include <stdio.h>
#include <unistd.h>

int main()
{
    char name[] = "666.txt";
    int ret = access(name, R_OK);
    if (ret == -1)
    {
        printf("you can not read \"%s\"\n", name);
    }
    printf("you can read \"%s\"\n", name);
}

chmod 和 chown 函數

修改文件權限,修改全部者和所屬用戶。

#include <sys/stat.h>

int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);
#include <unistd.h>

int chown(const char *path, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int lchown(const char *path, uid_t owner, gid_t group);

rename 函數

改變文件的名字或路徑。

#include <stdio.h>

int rename(const char *oldpath, const char *newpath);

getcwd 函數

獲取當前的工做目錄。能夠經過返回值或入參 buf 返回當前的絕對路徑。

#include <unistd.h>

char *getcwd(char *buf, size_t size);
char *getwd(char *buf); /* 已經廢棄 */
char *get_current_dir_name(void);

chdir 和mkdir 函數

更改當前目錄,建立新目錄。

#include <unistd.h>

int chdir(const char *path);
int fchdir(int fd);

示例:

#include <unistd.h>

int main()
{
    char name[] = "new_dir";
    char buf[100];
    mkdir(name);
    chdir(name);
    char *pwd = getcwd(buf, 100);
    printf("%s\n", pwd);
    printf("%s\n", buf);
    return 0;
}

opendir 和 readdir 函數

打開目錄,讀目錄。man 2 opendir 沒找到描述,最好別用,能夠用封裝好的 C 庫函數。

int readdir(unsigned int fd, struct old_linux_dirent *dirp, unsigned int count);

dup 和 dup2 函數

複製文件描述符。

#include <unistd.h>

int dup(int oldfd);
int dup2(int oldfd, int newfd);

fcntl 函數

修改文件描述符。

#include <unistd.h>
#include <fcntl.h>

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

綜合示例

#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>

int main(int argc, char* argv[])
{
	int fd, res;
	char str[] = "hello world!";
	char buf[20] = {0};
	char name[] = "666.txt";
	
	fd = open(name, O_WRONLY);
	if (fd < 0)
	{
		printf("open file %s failed, errorno = %d\n", name, errno);
		return -1;
	}
	res = write(fd, str, sizeof(str));
	printf("write %d bytes to \"%s\"\n", res, name);
	fsync(fd);
	close(fd);
	
	fd = open(name, O_RDONLY);
	if (fd < 0) return -1;
	res = read(fd, buf, sizeof(buf));
	printf("read output is:\n%s\n", buf);
	printf("read %d bytes from \"%s\"", res, name);
	return 0;
}
相關文章
相關標籤/搜索