版權(quán)聲明:本文為 cdeveloper 原創(chuàng)文章,可以隨意轉(zhuǎn)載闷袒,但必須在明確位置注明出處庐舟!
底層輸入輸出(Low-Level Input/Output)
這篇博客主要介紹 Linux 原生的 IO 操作(Low IO)滞造,你可能會(huì)想不是有跨平臺(tái)的 ANSI C 可以使用么河胎,為啥還要學(xué)習(xí)底層 IO ? 有以下 4 個(gè)原因:
- 用于讀取大塊的二進(jìn)制文件
- 在解析之前將整個(gè)文件讀入內(nèi)核
- 執(zhí)行數(shù)據(jù)傳輸以外的操作
- 將描述符傳遞給子進(jìn)程
你需要知道這 4 個(gè)使用底層 IO 的原因讳嘱,在以后遇到實(shí)際的情況時(shí)能夠想到利用底層 IO 來解決幔嗦。因?yàn)榈讓虞斎胼敵龅暮瘮?shù)也有很多,這篇博客主要介紹 5 個(gè)最常用沥潭,最基本的底層 IO 函數(shù):
- 打開文件:
open
- 關(guān)閉文件:
close
- 讀取文件:
read
- 寫入文件:
write
- 操作文件指針:
lseek
如果你還有興趣學(xué)習(xí)其他的底層 IO 函數(shù)邀泉,建議你查看 glibc 的官方底層 IO 的學(xué)習(xí)資料,那是最好钝鸽,最權(quán)威的資料汇恤,下面就一起來看看這 5 個(gè)函數(shù)的用法。
open & close
我們操作 IO 首先要學(xué)會(huì)的就是打開和關(guān)閉文件寞埠,我們使用 open
和 close
這兩個(gè)函數(shù)屁置,他們的聲明如下(man 2 open):
open
// open 需要 3 個(gè)頭文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/*
* filename: 要打開的文件名稱
* flags: 打開標(biāo)記,例如:O_CREAT | ORDWR 表示文件不存在就創(chuàng)建仁连,并且可讀可寫
* mode: 打開權(quán)限
* return: 成功蓝角,返回一個(gè)新的文件描述符 fd,失敗返回 -1饭冬,并設(shè)置 errno
*/
int open (const char *filename, int flags, mode t mode);
打開文件也可以用 create使鹅,不過這個(gè)函數(shù)已經(jīng)棄用了!
close
再來看看 close昌抠,關(guān)閉文件會(huì)有如下結(jié)果:
- 文件描述符被取消分配
- 文件上進(jìn)程所擁有的任何記錄鎖都將被解鎖
- 當(dāng)與管道或
FIFO
相關(guān)聯(lián)的所有文件描述符都已關(guān)閉時(shí)患朱,任何未讀數(shù)據(jù)被丟棄
#include <unistd.h>
/*
* fd: 要關(guān)閉的文件的描述符
* return: 成功返回 0,失敗返回 -1炊苫,并設(shè)置 errno
*/
int close(int fd);
實(shí)例 1: open_close.c
我們來看一個(gè)簡(jiǎn)單的例子:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char* argv[]) {
if (argc < 2) {
printf("please input filename:\n");
exit(1);
}
// argv[1] = "1.txt", 打開的文件不存在就創(chuàng)建 | 可讀可寫裁厅,666(rw-rw-rw)
int fd = open(argv[1], O_CREAT | O_RDWR, 666);
if (fd < 0) {
printf("file open fail.\n");
exit(1);
} else {
printf("file open success.\n");
// 必須關(guān)閉文件
close(fd);
printf("file closed.\n");
}
return 0;
}
編譯:
gcc open_close.c -o open_close
運(yùn)行,我們沒有新建 1.txt
文件:
./open_close 1.txt
file open success.
file close success.
文件打開成功侨艾,并且當(dāng)前目錄下也有了一個(gè) 1.txt
文件了执虹,說明我們指定的 O_CREAT
標(biāo)記使得程序建立了這個(gè)文件。這兩個(gè)函數(shù)基本用法就是這樣唠梨,更多更詳細(xì)的用法還需要你自己到 glibc 官網(wǎng)或者 man 2 open
去找袋励。
read & write
我們?cè)诖蜷_文件后肯定會(huì)做的就是讀寫文件了,不然你打開文件干嘛当叭,我們來看看讀文件的 API:
read
read
函數(shù)從文件描述符 filedes
指定的文件中讀取 size
個(gè)字節(jié)茬故,存儲(chǔ)到 buffer
中。
#include <unistd.h>
/*
* filedes: 要讀取的文件描述符
* buffer: 存儲(chǔ)讀取字節(jié)的緩沖區(qū)
* size: 要讀取的大小
* return: 成功返回讀取的字節(jié)數(shù)蚁鳖,失敗返回 -1磺芭,并設(shè)置 errno
*/
ssize_t read (int filedes, void *buffer, size t size);
write
write
函數(shù)從 buffer
中取出 size
個(gè)字節(jié)的數(shù)據(jù),寫到 filedes
描述符表示的文件中醉箕。
#include <unistd.h>
/*
* filedes: 寫入的文件描述符
* buffer: 存儲(chǔ)待寫入數(shù)據(jù)的緩存區(qū)
* size: 要寫入的字節(jié)數(shù)
* return: 成功返回寫入的字節(jié)數(shù)钾腺,失敗返回 -1甘邀,并設(shè)置 errno
*/
ssize_t write (int filedes, const void *buffer, size t size);
實(shí)例 2: read_write.c
來看一個(gè)簡(jiǎn)單的讀寫文件的例子:讀取 file1 的內(nèi)容,寫到 file2 中垮庐,相當(dāng)于文件拷貝
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char* argv[]) {
// 1. 打開兩個(gè)文件
int fd1 = open(argv[1], O_CREAT | O_RDWR, 0666);
int fd2 = open(argv[2], O_CREAT | O_RDWR, 0666);
if ((fd1 < 0) || (fd2 < 0)) {
printf("file open fail.\n");
exit(1);
} else {
printf("file1 open success: fd1 = %d\n", fd1);
printf("file2 open success: fd2 = %d\n", fd2);
char buf[1024];
// clear
memset(buf, 0, 1024);
int read_length = 0;
int write_length = 0;
// 2. 將文件 1 的內(nèi)容讀取到 buf 中
if ((read_length = read(fd1, buf, 1024)) != -1) {
// 3. 將 buf 的內(nèi)容寫入到文件 2 中
if (read_length == write(fd2, buf, read_length))
printf("write to fd2 success.\n");
else
printf("write to fd2 falil.\n");
} else {
printf("read fd1 fail.\n");
}
// 必須關(guān)閉 2 個(gè)文件
close(fd1);
close(fd2);
printf("file1 close success.\n");
printf("file2 close success.\n");
}
return 0;
}
編譯:
gcc read_write.c -o read_write
運(yùn)行,1.txt
的內(nèi)容是 hello world
:
./read_write 1.txt 2.txt
file1 open success: fd1 = 3
file2 open success: fd2 = 4
write to fd2 success.
file1 close success.
file2 close success.
寫入成功坞琴,查看下 2.txt 的內(nèi)容:
cat 2.txt
hello world
發(fā)現(xiàn)成功寫入 hello world
到 2.txt 文件了哨查,并且注意到 fd1 = 3, fd2 = 4
,這也說明前面的 3 個(gè)文件描述符被系統(tǒng)使用了剧辐。
lseek
lseek
用來移動(dòng)文件指針寒亥,什么是文件指針呢?你可以理解為當(dāng)前讀取或者寫入的位置荧关,我們移動(dòng)這個(gè)指針可以控制讀取或者寫入數(shù)據(jù)的位置溉奕,聲明如下:
#include <sys/types.h>
#include <unistd.h>
/*
* filedes: 要操作的文件描述符
* offset: 根據(jù)當(dāng)前 whence 的偏移量
* whence: 指定當(dāng)前文件指針的位置
* return: 成功返回設(shè)置后的文件位置,可以使用 `SEEK_CUR` 查看當(dāng)前文件指針位置忍啤,
* 失敗返回 -1 并設(shè)置 errno
*/
off_t lseek (int filedes, off t offset, int whence);
其中 whence
參數(shù)需要特別注意加勤,它有 3 種情況:
-
SEEK_SET
:設(shè)置文件指針指向文件開始并偏移 offset 字節(jié)處 -
SEEK_CUR
:設(shè)置文件指針只想當(dāng)前位置偏移 offset 字節(jié)處 -
SEEK_END
:設(shè)置文件指針指向文件末尾偏移 offset 字節(jié)處
實(shí)例 3:file_length.c
我們可以使用 int file_length = lseek(fd, 0, SEEK_END)
來求文件的長(zhǎng)度,這個(gè)操作經(jīng)常被使用同波。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
// 打開文件
int fd = open(argv[1], O_CREAT | O_RDWR, 0666);
if (fd < 0) {
printf("file open fail.\n");
exit(1);
} else {
printf("file1 open success: fd = %d\n", fd);
// 得到文件長(zhǎng)度
int file_length = lseek(fd, 0, SEEK_END);
// 輸出文件長(zhǎng)度
printf("file_length = %d\n", file_length);
// 關(guān)閉文件
close(fd);
printf("file1 close success.\n");
}
return 0;
}
編譯:
gcc file_length.c -o file_length
運(yùn)行鳄梅,注意到 1.txt
中的 hello world
加上 '\0' 一共 12 個(gè)字符:
./file_length 1.txt
file1 open success: fd = 3
file_length = 12
file1 close success.
打印出 12,計(jì)算正確啦未檩。
結(jié)語
到此戴尸,我們就學(xué)習(xí)了 5 個(gè)底層的 IO 函數(shù),并實(shí)際練習(xí)了 3 個(gè)例子冤狡,把這 3 個(gè)例子搞清楚孙蒙,基本的用法也就掌握的差不多了,更加詳細(xì)的用法可以查看系統(tǒng)提供的 man 手冊(cè) man 2 open
等悲雳,或者查閱 glibc 官方文檔挎峦,祝你學(xué)習(xí)愉快。
最后怜奖,感謝你的閱讀浑测,我們下次再見 :)