从 0 开始学习 Linux 系列之「12.必须掌握的 5 个底层 IO 函数」

LowIO

版权声明:本文为 cdeveloper 原创文章,可以随意转载,但必须在明确位置注明出处!

底层输入输出(Low-Level Input/Output)

这篇博客主要介绍 Linux 原生的 IO 操作(Low IO),你可能会想不是有跨平台的 ANSI C 可以使用么,为啥还要学习底层 IO ? 有以下 4 个原因:

  1. 用于读取大块的二进制文件
  2. 在解析之前将整个文件读入内核
  3. 执行数据传输以外的操作
  4. 将描述符传递给子进程

你需要知道这 4 个使用底层 IO 的原因,在以后遇到实际的情况时能够想到利用底层 IO 来解决。因为底层输入输出的函数也有很多,这篇博客主要介绍 5 个最常用,最基本的底层 IO 函数

  1. 打开文件:open
  2. 关闭文件:close
  3. 读取文件:read
  4. 写入文件:write
  5. 操作文件指针:lseek

如果你还有兴趣学习其他的底层 IO 函数,建议你查看 glibc 的官方底层 IO 的学习资料,那是最好,最权威的资料,下面就一起来看看这 5 个函数的用法。

open & close

我们操作 IO 首先要学会的就是打开和关闭文件,我们使用 openclose 这两个函数,他们的声明如下(man 2 open):

open

// open 需要 3 个头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/*
 * filename: 要打开的文件名称
 * flags: 打开标记,例如:O_CREAT | ORDWR 表示文件不存在就创建,并且可读可写
 * mode: 打开权限
 * return: 成功,返回一个新的文件描述符 fd,失败返回 -1,并设置 errno
 */
int open (const char *filename, int flags, mode t mode);

打开文件也可以用 create,不过这个函数已经弃用了!

close

再来看看 close,关闭文件会有如下结果:

  1. 文件描述符被取消分配
  2. 文件上进程所拥有的任何记录锁都将被解锁
  3. 当与管道或 FIFO 相关联的所有文件描述符都已关闭时,任何未读数据被丢弃
#include <unistd.h>

/*
 * fd: 要关闭的文件的描述符
 * return: 成功返回 0,失败返回 -1,并设置 errno
 */
int close(int fd);

实例 1: open_close.c

我们来看一个简单的例子:

#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", 打开的文件不存在就创建 | 可读可写,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");
        // 必须关闭文件
        close(fd);
        printf("file closed.\n");
    }

    return 0;
}

编译:

gcc open_close.c -o open_close

运行,我们没有新建 1.txt 文件:

./open_close 1.txt
file open success.
file close success.

文件打开成功,并且当前目录下也有了一个 1.txt 文件了,说明我们指定的 O_CREAT 标记使得程序建立了这个文件。这两个函数基本用法就是这样,更多更详细的用法还需要你自己到 glibc 官网或者 man 2 open 去找。

read & write

我们在打开文件后肯定会做的就是读写文件了,不然你打开文件干嘛,我们来看看读文件的 API:

read

read 函数从文件描述符 filedes 指定的文件中读取 size 个字节,存储到 buffer 中。

#include <unistd.h>

/*
 * filedes: 要读取的文件描述符
 * buffer: 存储读取字节的缓冲区
 * size: 要读取的大小
 * return: 成功返回读取的字节数,失败返回 -1,并设置 errno
 */
ssize_t read (int filedes, void *buffer, size t size);

write

write 函数从 buffer 中取出 size 个字节的数据,写到 filedes 描述符表示的文件中。

#include <unistd.h>

/*
 * filedes: 写入的文件描述符
 * buffer: 存储待写入数据的缓存区
 * size: 要写入的字节数
 * return: 成功返回写入的字节数,失败返回 -1,并设置 errno
 */
ssize_t write (int filedes, const void *buffer, size t size);

实例 2: read_write.c

来看一个简单的读写文件的例子:读取 file1 的内容,写到 file2 中,相当于文件拷贝

#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. 打开两个文件
    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 的内容读取到 buf 中
        if ((read_length = read(fd1, buf, 1024)) != -1)  {
            // 3. 将 buf 的内容写入到文件 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");
        }
        
        // 必须关闭 2 个文件
        close(fd1);
        close(fd2);

        printf("file1 close success.\n");
        printf("file2 close success.\n");
    }

    return 0;
}

编译:

gcc read_write.c -o read_write

运行,1.txt 的内容是 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 的内容:

cat 2.txt

hello world

发现成功写入 hello world 到 2.txt 文件了,并且注意到 fd1 = 3, fd2 = 4,这也说明前面的 3 个文件描述符被系统使用了。

lseek

lseek 用来移动文件指针,什么是文件指针呢?你可以理解为当前读取或者写入的位置,我们移动这个指针可以控制读取或者写入数据的位置,声明如下:

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

/*
 * filedes: 要操作的文件描述符
 * offset: 根据当前 whence 的偏移量
 * whence: 指定当前文件指针的位置
 * return: 成功返回设置后的文件位置,可以使用 `SEEK_CUR` 查看当前文件指针位置,
 *         失败返回 -1 并设置 errno
 */
off_t lseek (int filedes, off t offset, int whence);

其中 whence 参数需要特别注意,它有 3 种情况:

  1. SEEK_SET:设置文件指针指向文件开始并偏移 offset 字节处
  2. SEEK_CUR:设置文件指针只想当前位置偏移 offset 字节处
  3. SEEK_END:设置文件指针指向文件末尾偏移 offset 字节处

实例 3:file_length.c

我们可以使用 int file_length = lseek(fd, 0, SEEK_END)求文件的长度,这个操作经常被使用。

#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);

        // 得到文件长度
        int file_length = lseek(fd, 0, SEEK_END);
        
        // 输出文件长度
        printf("file_length = %d\n", file_length);

        // 关闭文件
        close(fd);
        printf("file1 close success.\n");
    }

    return 0;
}

编译:

gcc file_length.c -o file_length

运行,注意到 1.txt 中的 hello world 加上 '\0' 一共 12 个字符

./file_length 1.txt
file1 open success: fd = 3
file_length = 12
file1 close success.

打印出 12,计算正确啦。

结语

到此,我们就学习了 5 个底层的 IO 函数,并实际练习了 3 个例子,把这 3 个例子搞清楚,基本的用法也就掌握的差不多了,更加详细的用法可以查看系统提供的 man 手册 man 2 open 等,或者查阅 glibc 官方文档,祝你学习愉快。

最后,感谢你的阅读,我们下次再见 :)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容