Linux编程学习笔记 | Linux IO学习[1] - 文件IO

系统调用与程序运行空间

在Linux操作系统中,为了提高系统的稳定性,保证内核的安全,程序运行时的内存空间被分为了用户空间和内核空间。普通应用程序工作在用户空间,不能直接访问内核空间。它们需要使用Linux系统提供给用户的一些"特殊接口" - 系统调用来安全地访问内核空间。

要对文件进行读写就需要使用Linux系统提供的一些系统调用。在这篇文章中我主要介绍 open() , write() , read() , lseek()close() 等函数,在下文中我会详细讲解这些函数的使用。

文件描述符

在Linux系统中,一切都可以被看作是文件,这包括:普通文件、目录文件、链接文件和设备文件。要访问文件,必须使用文件描述符。文件描述符是一个非负的整数,它是系统中被打开文件的索引。当打开或者创建一个文件时,内核会返回一个文件描述符;当需要读写文件时,也需要将相应的文件描述符作为参数传给读写函数。程序启动时,默认有3个文件描述符:

文件描述符 说明
0 STDIN_FILENO 标准输入
1 STDOUT_FILENO 标准输出
2 STDERR_FILENO 标准错误输出

如果此时创建或打开一个文件,这个文件的文件描述符就是3.

文件IO基本操作

打开/创建文件

open() 函数用于打开或者创建文件。其在打开或者创建文件时可以指定文件的属性及用户的权限等各种参数。要使用 open() 函数,需要包含 #include <sys/stat.h>#include <fcntl.h> 这两个头文件。下面是函数的说明:

int open(const char *path, int oflag, [mode_t mode]);

args:
    const char *path: 文件路径,可以是绝对,也可以是相对路径 
    int oflag       : 文件打开的方式
                        - O_RDONLY 只读打开
                        - O_WRONLY 只写打开
                        - O_RDWR   可读可写打开
                        以上3种必选一个,以下4种可以任意选择
                        - O_APPEND 追加打开,所写数据附加到文件末
                        - O_CREAT  若此文件不存在则创建它
                        - O_EXCL   若文件存在则报错返回 
                        - O_TRUNC  如果文件已存在,并且以只写或可读可写方式打开,则将其长度截断为0字节
    [mode_t mode]   : 文件权限,只有在创建文件时需要使用
    
return:
    文件描述符,非负整数是成功,-1是失败

open() 函数中,文件的打开方式不止上面的几种,这里只列举了常用的7种。注意,新建文件的权限不是直接等于 mode 的值,而是等于 mode & ~uname

写文件

当文件打开后,我们就可以向该文件写数据了。在Linux系统中,用 write() 向打开的文件写入数据,要使用这个函数,需要包含 #include <unistd.h> 。下面是函数的说明:

ssize_t write(int fildes, const void *buf, size_t nbyte);

args:
    int fildes     : 写入文件的文件描述符
    const void *buf: 写入数据在内存空间存储的地址
    size_t nbyte   : 期待写入数据的最大字节数
    
return:
    文件实际写入的字节数,非负整数是成功,-1是失败(磁盘已满或者超出该文件的长度等)

注意函数的返回类型是 ssize_tssize_tsize_t 类似,只是 ssize_t 表示有符号数。想了解更多 size_tssize_t 的区别请看这篇文章

读文件

同写文件类似,要使用读文件函数 read() ,需要包含 #include <unistd.h> 。下面是函数的说明:

ssize_t read(int fildes, void *buf, size_t nbyte);

args:
    int fildes  : 读取文件的文件描述符
    void *buf   : 读取数据在内存空间存储的地址
    size_t nbyte: 期待读取数据的最大字节数
    
return:
    文件实际读取的字节数,非负整数是成功,-1是失败

write() 一样, read() 函数的返回类型也是 ssize_t

文件的偏移量

在每个打开的文件中都有一个文件的偏移量,文件的偏移量会根据文件的读写而改变位置。我们可以通过 lseek() 函数来调整文件的偏移量。默认情况下,新打开文件的文件偏移量在文件的开始。同 write()read() 函数类似,要使用这个函数,需要包含 #include <unistd.h> 。下面是函数的说明:

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

args:
    int fildes  : 修改文件的文件描述符
    off_t offset: 文件偏移量移动的距离
    int whence  : 文件偏移量的基址
                    - SEEK_SET 文件开始处
                    - SEEK_CUR 文件当前位置
                    - SEEK_END 文件结束处
    
return:
    当前文件指针的位置,非负整数是成功,-1是失败

off_tssize_t 类似,都是有符号数。

关闭文件

当文件不再被使用时,可以调用 close() 函数来关闭被打开的文件。
除了用 close() 显示地关闭文件外,通过结束进程也能隐式地关闭被该进程打开的所有文件。要使用该函数,需要包含 #include <unistd.h> 。下面是函数的说明:

int close(int fildes);

args:
   int fildes: 要关闭文件的文件描述符
   
return:
    文件关闭状态,0是成功,-1是失败

文件IO实例

文件基本操作

这是一个简单的文件基本操作实例。在这个例子中,程序分两次将内存中的字符串写入文件,然后又将文件内容读回内存空间。

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

/**
 * This is a simple example for using open(), write(), read(), lseek() and close().
 */
int main(int argc, char *argv[])
{
    int fd;
    ssize_t wr_size, rd_size;
    char buffer[128];
    char string_1[30], string_2[30] = "This is the second line!\n";
    char *path = "./file_io.log"; 
    
    fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 511);
    if (fd < 0) {
        printf("File create fail...\n");
        return -1; 
    } else {
        printf("File create success...\n");
    }
    
    /* write the first line to file_io.log */
    strcpy(string_1, "This is a demo for file_io!\n");
    wr_size = write(fd, string_1, strlen(string_1));
    if (wr_size < 0) {
        printf("File write 1 fail...\n");
        printf("wr_size = %d\n", wr_size);
        return -1; 
    } else {
        printf("File write 1 success...\n");
        printf("wr_size = %d\n", wr_size);
    }
    
    /* write the second line to file_io.log 
     * in this case, we only write 10 bytes data from string_2 to file.
     */
    wr_size = write(fd, string_2, 10);
    /* add "\0"(not '\0'!!) to the end of the second line */ 
    wr_size = write(fd, "\0", 1);
    if (wr_size < 0) {
        printf("File write 2 fail...\n");
        printf("wr_size = %d\n", wr_size);
        return -1; 
    } else {
        printf("File write 2 success...\n");
        printf("wr_size = %d\n", wr_size);
    }
    
    /* decrease current file offset by 20 bytes */
    lseek(fd, -20, SEEK_CUR);

    rd_size = read(fd, buffer, 100); 
    if (rd_size < 0) {
        printf("File read_1 fail...\n");
        printf("rd_size = %d\n", rd_size);
        return -1; 
    } else {
        printf("File read_1 success...\n");
        printf("rd_size = %d,\nbuffer = %s\n", rd_size, buffer);
    } 

    close(fd);
    
    return 0; 
}

编译并运行该程序,程序和文件输出结果如下:

程序输出结果
文件输出结果

对于上面的例子,有几点需要注意:

  1. 在40行处, wr_size = write(fd, string_2, 10); 我们写入的字节数是小于 string_2 中的字节数的。如果想要写入的字节数大于 string_2 中的字节数,那 string_2 外的字节也会写入文件(这些额外的字节不是我们希望要的)。比如我们将40行改为 wr_size = write(fd, string_2, 100); 其输出结果如下:
实际写入的字节数大于需要写入的字节数

2)如果注释掉53行,则读出的字节数为0,因为此时文件的偏移量处于文件的尾部。

忘记修改文件偏移量

简易版CP指令

这是一个模仿Linux cp指令的小程序,这里并没有考虑效率,也没有考虑特殊情况,只是简单地实现其功能。

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

/* 
 * This a simple version of cp command. 
 */
int main(int argc, char *argv[])
{
    int fd1, fd2;
    ssize_t rd_size;
    char buffer[128];
    
    if (argc != 3) {
        printf("You should enter enter 2 parameters\n"); 
        return -1;
    }

    fd1 = open(argv[1], O_RDONLY);
    if (fd1 < 0) {
        printf("File %d does not exist...\n", fd1);
        return -1; 
    }
    fd2 = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 511); 
    if (fd2 < 0) {
        printf("File %d open fail...\n", fd2);
        return -1; 
    }

    while(read(fd1, buffer, 1))
        write(fd2, buffer, 1);

    close(fd1);
    close(fd2);
    
    return 0; 
}

编译并运行该程序,程序输出结果如下:

模仿CP指令

总结

这篇文章主要介绍了如何使用文件IO的系统调用函数对文件进行操作,文中出现的代码都可在我的[github][7]上找到。

如果觉得本文对你有帮助,请多多点赞支持,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,591评论 18 139
  • 一、温故而知新 1. 内存不够怎么办 内存简单分配策略的问题地址空间不隔离内存使用效率低程序运行的地址不确定 关于...
    SeanCST阅读 7,774评论 0 27
  • linux资料总章2.1 1.0写的不好抱歉 但是2.0已经改了很多 但是错误还是无法避免 以后资料会慢慢更新 大...
    数据革命阅读 12,130评论 2 34
  • 开始本章学习之前了解一些概念: 1、带缓存和不带缓存IO 参考系统调用,用户程序通过系统级别API调用系统函数将请...
    lifesmily阅读 412评论 0 0
  • 在我职业生涯的十年里,遇见过太多号称财务自由的人,他们中的一部分财务状态确实非常良好,大部分都是借钱让自己过得潇洒...
    南风大人阅读 497评论 0 3