前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【Linux基础IO】一、系统IO

【Linux基础IO】一、系统IO

作者头像
利刃大大
发布2025-02-28 08:43:08
发布2025-02-28 08:43:08
5300
代码可运行
举报
文章被收录于专栏:csdn文章搬运
运行总次数:0
代码可运行

Ⅰ. 前言

​ 我们知道 C语言 有文件操作接口,那么当然 C++JAVA 这些语言都有文件操作接口。但是最让人烦的问题是这些语言之间的 IO 接口都是不一样的,这样子导致我们学习成本很高!

​ 这些语言拥有文件操作接口的目的找到文件,然后对文件进行操作。那么文件是在磁盘上,磁盘是属于硬件。对于硬件的访问只有操作系统才能进行所有人想访问磁盘都不能绕开操作系统,C语言也好,其他语言也罢都是人表达出意思让操作系统理解我们想要干嘛,所以任何上层语言想要进行对磁盘进行操作,都会使用操作系统提供的接口。

​ 所以我们只要搞清楚了操作系统提供的 IO 接口,那么其它语言只是对其的一个包装而已!下面我们一起来认识一下系统的IO接口!

Ⅱ. 系统IO接口

一、open接口

代码语言:javascript
代码运行次数:0
复制
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags, mode_t mode);
// 功能:打开或者创建一个新文件或设备
// 返回值:打开成功返回打开文件的文件描述符(后面会讲到),打开失败则返回-1

// 参数:
// 	1、pathname代表要打开或创建的目标文件
//  2、flags代表打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags
// 	3、mode代表打开文件时所带的权限,对于普通文件一般是0666(要考虑掩码umask的值)

其中对于参数 flags 有下面几种:

  • O_RDONLYread-only):只读打开
  • O_WRONLYwrite-only):只写打开
  • O_RDWR(read-write):读和写打开

打开上面这三个常量,必须指定一个且只能指定一个!

  • O_CREAT:若文件不存在,则创建它。需要使用 mode 选项,来**指明新文件的访问权限**
  • O_APPEND:追加写
  • O_TRUNCtruncate):清空文件的内容

​ 上面这些 flags 选项的 本质其实就是宏用不同的比特位来表示不同的信号,比特位之间通过按位或传递选项,下面举个例子大家就懂原理了:

代码语言:javascript
代码运行次数:0
复制
#include <stdio.h>

// 用不同的比特位来表示不同的信号
#define ONE (1 << 0) // 0
#define TWO (1 << 1) // 1
#define THREE (1 << 2) // 2
#define FOUR (1 << 3)  // 4
void showSig(int flags)
{
    if(flags & ONE) printf("one\n");    
    else if(flags & TWO) printf("two\n");    
    else if(flags & THREE) printf("three\n");    
    else if(flags & FOUR) printf("four\n");
}
int main()
{
    showSig(ONE);    
    printf("------------------------\n");   
    showSig(TWO);    
    printf("------------------------\n");    
    showSig(ONE|TWO);    
    printf("------------------------\n");    
    showSig(ONE|TWO|THREE);    
    printf("------------------------\n");    
    showSig(ONE|TWO|THREE|FOUR);    
    printf("------------------------\n");
    return 0;
}

// 运行结果
one
------------------------
two
------------------------
one
------------------------
one
------------------------
one
------------------------

​ 下面我们先介绍 close 接口再进行代码测试!

二、close接口

代码语言:javascript
代码运行次数:0
复制
 #include <unistd.h>
int close(int fd);
// 功能:关闭对应文件描述符指向的文件
// 返回值:成功关闭返回0,失败的话返回-1,并且设置errno为错误信息
① 测试一下open的返回值也就是fd的值
代码语言:javascript
代码运行次数:0
复制
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    umask(0); // 将子进程中的umask设为想要的值,这里设为0
    int fd = open("log.txt", O_WRONLY | O_CREAT, 0664);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    printf("fd: %d\n", fd);

    // 关闭文件
    close(fd);
    return 0;
}

运行结果:
fd: 3

​ 这里的 fd3 是巧合吗?当然不是的,这个是 文件描述符(file descripter) ,并且我们也可以猜到,这也可能和文件指针是有关系的,这个我们后面会讲!!!

② 以不同的权限新建两个文件(默认掩码umask为0002)
代码语言:javascript
代码运行次数:0
复制
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    int fd1 = open("log1.txt", O_WRONLY | O_CREAT, 0664); // 权限设为0664
    if(fd1 < 0)
    {
        perror("open");
        return 1;
    }

    int fd2 = open("log2.txt", O_WRONLY | O_CREAT, 0644); // 权限设为0644
    if(fd2 < 0)
    {
        perror("open");
        return 1;
    }

    // 关闭文件
    close(fd1);
    close(fd2);
    return 0;
}

三、write接口

代码语言:javascript
代码运行次数:0
复制
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
// 功能:向文件描述符中写入内容
// 返回值:成功写入的话返回写入的字节个数,失败的话则返回-1,并且设置errno为错误信息

// 参数:
//  1、fd表示要被写入的文件描述符
//  2、buf表示写入的内容(可能是文本类也可能是二进制类,所以这里为void*)
//	3、count表示写入内容的大小,不算入'\0'(以字节为单位)

先来解析一下上面的参数设定:

  • buf 是要写入的内容,有可能是文本类也有可能是二进制类,但是操作系统不管,因为在操作系统看来,它要处理的都是二进制类的内容,而具体处理文本的内容其实是语言帮我们完成,这个要注意!所以buf的参数类型是 void* ,不会被传入的内容影响!
  • count 的问题主要是在我们传入大小时候,有可能会因为受语言级别的函数影响容易传错大小导致乱码,要 注意的是操作系统接收的大小里面是不需要包括‘\0’,至于语言级别的函数想要传入包括 ‘\0’ ,这是语言的规定,和我们系统接口是没有关系的,比如说传入一个 buffer 字符串,那么大小只需要传入 strlen(buffer) 即可,不需要加一!

❓ 还要注意的一个细节:

  • 我们 打开文件的时候默认是不会清空文件的内容的,所以对打开文件写入内容时候,如果里面含有旧内容,只会对旧内容进行覆盖,之所以我们之前调用的语言级别的函数会帮我们清空,那是它们帮我们做的额外工作,所以如果我们想要清空旧内容,不要忘记在打开文件的时候加上 O_TRUNC 这个选项!
① 将新内容写入新文件
代码语言:javascript
代码运行次数:0
复制
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    // 注意要清空内容,带上O_TRUNC
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

    // 将字符串写到文件中
    char buffer[1024];
    int cnt = 5;
    while(cnt--)
    {
        // 将字符串和数字通过sprintf转化为字符串
        sprintf(buffer, "%s:%d\n", "lirendada", cnt + 1);

        // 将新内容写入文件,注意系统接口是不包括'\0'的大小的
        write(fd, buffer, strlen(buffer));
    }

    // 关闭文件
    close(fd);
    return 0;
}
② 向文本中追加内容
代码语言:javascript
代码运行次数:0
复制
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    // 注意不能清空内容,并带上O_APPEND
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

    char buffer[1024];
    int cnt = 5;
    while(cnt--)
    {
        // 将字符串和数字通过sprintf转化为字符串
        sprintf(buffer, "%s:%d\n", "after append!!!", cnt + 10);

        // 追加新内容,注意系统接口是不包括'\0'的大小的
        write(fd, buffer, strlen(buffer));
    }

    // 关闭文件
    close(fd);
    return 0;
}

四、read接口

代码语言:javascript
代码运行次数:0
复制
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
// 功能:从文件中读取内容到指定位置
// 返回值:成功读取的话返回读取内容的字节数,实际上该返回值就在'\0'的位置;失败的话返回-1,并且设置errno为错误信息

// 参数:
//  1、fd表示读取内容的文件描述符
//  2、buf表示读取内容的目标处
//  3、count表示读取内容的大小(以字节为单位)

有几个值得注意的点:

  • read 在读取文件中的内容后,假如我们放到字符数组中,它是不会帮我们添加 \0 的,所以我们必须要手动添加,而添加 \0 的位置刚好就是返回值的位置,因为返回值代表内容的大小!详细的看下面代码是如何读取的!
  • 一般来说文件中是没有以 \0 结尾的,所以我们在读取的时候,如果是用字符数组接收的话,那么要多留一个位置出来,方便我们读取后手动添加 \0
代码语言:javascript
代码运行次数:0
复制
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    int fd = open("log.txt", O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

    // 读取文件中的信息
    char buffer[1024];
    // 这里sizeof - 1是为了留位置给我们自己添加的'\0'腾出位置,因为文件中是不含'\0'的,要多预留一个位置出来
    ssize_t num = read(fd, buffer, sizeof(buffer) - 1);

    // 大于0表示读取成功,并将buffer的末尾设为'\0',因为系统接口是不为我们添加'\0'的
    if(num != 0)
    {
        buffer[num] = '\0';
        printf("%s", buffer);
    }

    // 关闭文件
    close(fd);
    return 0;
}

// 运行结果:
[liren@VM-8-2-centos cfile]$ ./file 
lirendada:5
lirendada:4
lirendada:3
lirendada:2
lirendada:1
after append!!!:14
after append!!!:13
after append!!!:12
after append!!!:11
after append!!!:10
[liren@VM-8-2-centos cfile]$ 

5、其它系统接口

​ 比如说 lseek 等其它接口,我们学以上接口就基本足够我们了解系统接口和语言级别接口的区别了,我们现实情况下也不会说去专门调用系统级别的接口,所以学习它们是为了更好的区分底层所做的逻辑工作!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Ⅰ. 前言
  • Ⅱ. 系统IO接口
    • 一、open接口
    • 二、close接口
      • ① 测试一下open的返回值也就是fd的值
      • ② 以不同的权限新建两个文件(默认掩码umask为0002)
    • 三、write接口
      • ① 将新内容写入新文件
      • ② 向文本中追加内容
    • 四、read接口
    • 5、其它系统接口
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档