Linux:基础IO(二)

打开⽂件的⽅式不仅仅是fopen,ifstream等流式,他们是语⾔层的⽅案,其实系统才是打开⽂件最底层的⽅案。所以这篇博客将讲述系统⽂件IO函数

1.初识open函数

输入命令man 2 open即可查看系统调用open函数的内容

​​

如果想查看一个文件,就使用两个参数的open

如果想要w一个文件,就使用三个参数的open

其中mode就是标志位

2.标志位flags

1.什么是标志位

标志位(也常叫状态位 / 标志位掩码 )是编程中一种利用整数的二进制位来表示多个独立的状态、命令或属性的设计技巧。简单来说:

  • 一个整数在内存中以二进制形式存储(比如 32 位整数有 32 个二进制位,每位是 0 或 1);
  • 我们将每一个二进制位映射为一个独立的 "开关" 或 "命令"(1 表示开启 / 生效,0 表示关闭 / 无效);
  • 再通过宏定义给每个位赋予具体的语义(比如第 0 位代表 "读",第 1 位代表 "写");
  • 最终可以通过位运算 (位或|、位与&等)来组合多个标志,或判断某个标志是否生效。

你提到的 "可读可写不需要传两个参数,而是传读宏 | 写宏",正是标志位的核心用法 ------用一个整数参数承载多个独立的命令 / 状态,而非多个参数。

从本质上讲,标志位就是位图(BitMap)的一种简化应用:用整数的每一位作为 "位",用整数值作为 "图",实现对多个布尔状态的高效存储和操作

2.传递标志位

下面我们使用自己的编写的宏来代替表示标志位,通过| & 运算来传递标志位

flags.c :

cpp 复制代码
#include <stdio.h>

#define ONE_FLAG 1 << 0 //0000 0000... 0000 0001
#define TWO_FLAG 1 << 1 //0000 0000... 0000 0010
#define THREE_FLAG 1 << 2 //0000 0000... 0000 0100
#define FOUR_FLAG 1 << 3 //0000 0000... 0000 1000

void Print(int mode)
{
    if (mode & ONE_FLAG)
    {
        printf("ONE! ");
    }
    if (mode & TWO_FLAG)
    {
        printf("TWO! ");
    }
    if (mode & THREE_FLAG)
    {
        printf("THREE! ");
    }
    if (mode & FOUR_FLAG)
    {
        printf("FOUR! ");
    }
}

int main()
{
    Print(ONE_FLAG);
    printf("\n");
    Print(ONE_FLAG | TWO_FLAG);
    printf("\n");
    Print(ONE_FLAG | TWO_FLAG | THREE_FLAG);
    printf("\n");
    Print(ONE_FLAG | TWO_FLAG | THREE_FLAG | FOUR_FLAG);
    printf("\n");
    return 0;
}

运行结果如下:

实现了通过传入不同的标志位打印出了对应的结果

3.使用open函数

下面是myfile.c

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    int fd = open("log.txt", O_CREAT | O_WRONLY, 0666);
    if (fd < 0)
    {
        perror("fd open");
        return 1;
    }
    return 0;
}

运行结果如下:

如果你仔细观察就会发现,明明我们设置的权限位是0666,开始创建出来的log.txt是664,这是为什么呢?

那当然是mask啦,每个进程都会有自己的mask(掩码),我们可以使用umask(0)来将该进程掩码调为0,这样我们创造的权限就不会变啦

myfile.c:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    umask(0);
    int fd = open("log.txt", O_CREAT | O_WRONLY, 0666);
    if (fd < 0)
    {
        perror("fd open");
        return 1;
    }
    return 0;
}

4.使用close函数

我们打开了log.txt,当然得关闭一下,所以我们来学习关闭函数--close

其实很简单,只需要传入fd即可关闭

myfile.c:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    umask(0);
    int fd = open("log.txt", O_CREAT | O_WRONLY, 0666);
    if (fd < 0)
    {
        perror("fd open");
        return 1;
    }
    printf("fd : %d\n", fd);
    close(fd);
    return 0;
}

运行结果如下:

5.使用write函数

其中fd是你打开的文件描述符,buf是你要写的内容,count是最多写入多少数量

基础使用

下面是myfile.c代码

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    umask(0);
    int fd = open("log.txt", O_CREAT | O_WRONLY, 0666);
    if (fd < 0)
    {
        perror("fd open");
        return 1;
    }
    int cnt = 5;
    const char* buffer = "hello bit\n";
    while (cnt--)
    {
        write(fd, buffer, strlen(buffer));
    }
    //printf("fd : %d\n", fd);
    close(fd);
    return 0;
}

运行结果如下,成功向log.txt写入五条消息

那么我们现在再写入一条短点的字符串呢

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    umask(0);
    int fd = open("log.txt", O_CREAT | O_WRONLY, 0666);
    if (fd < 0)
    {
        perror("fd open");
        return 1;
    }
    //int cnt = 5;
    //const char* buffer = "hello bit\n";
    const char* buffer = "abcd";
    write(fd, buffer, strlen(buffer));
    //while (cnt--)
    //{
    //    write(fd, buffer, strlen(buffer));
    //}
    //printf("fd : %d\n", fd);
    close(fd);
    return 0;
}

我们惊奇的发现居然不是清空文件再写入,而是直接覆盖式的写入!

那是因为在我们调用c语言封装的fwrite(w),它默认是清空的,但是在系统write中,只有我们加入对应标志位(O_TRUNC)才能清空

myfile.c :

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    umask(0);
    //int fd = open("log.txt", O_CREAT | O_WRONLY, 0666);
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    if (fd < 0)
    {
        perror("fd open");
        return 1;
    }
    //int cnt = 5;
    //const char* buffer = "hello bit\n";
    const char* buffer = "abcd";
    write(fd, buffer, strlen(buffer));
    //while (cnt--)
    //{
    //    write(fd, buffer, strlen(buffer));
    //}
    //printf("fd : %d\n", fd);
    close(fd);
    return 0;
}

那如果我们不想覆盖写入,也不想清空写入,而是追加写入该怎么办呢,只需要加入宏定义标志位--O_APPEND

myfile.c:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    umask(0);
    //int fd = open("log.txt", O_CREAT | O_WRONLY, 0666);
    //int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);
    if (fd < 0)
    {
        perror("fd open");
        return 1;
    }
    //int cnt = 5;
    //const char* buffer = "hello bit\n";
    const char* buffer = "abcd";
    write(fd, buffer, strlen(buffer));
    //while (cnt--)
    //{
    //    write(fd, buffer, strlen(buffer));
    //}
    //printf("fd : %d\n", fd);
    close(fd);
    return 0;
}

运行结果符合预期:

我们回到开头,open函数并非是char* buf,而是void* buf,说明它可以传入任意类型的参数

我们又学过,我们可以以字符形式写入,也可以以二进制形式写入

那么我考考你,我们要写入123456是写入1, 2, 3, 4, 5, 6还是直接写入123456呢?

答案就在下面的代码中:

myfile.c:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    umask(0);
    //int fd = open("log.txt", O_CREAT | O_WRONLY, 0666);
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    //int fd = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);
    if (fd < 0)
    {
        perror("fd open");
        return 1;
    }
    //int cnt = 5;
    //const char* buffer = "hello bit\n";
    //const char* buffer = "abcd";
    int n = 123456;
    //write(fd, buffer, strlen(buffer));
    write(fd, &n, sizeof(n));
    //while (cnt--)
    //{
    //    write(fd, buffer, strlen(buffer));
    //}
    //printf("fd : %d\n", fd);
    close(fd);
    return 0;
}

运行并且使用vim打开log.txt

这就是以123456的二进制写入

如果我们将123456转化为字符串写入

myfile.c:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    umask(0);
    //int fd = open("log.txt", O_CREAT | O_WRONLY, 0666);
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    //int fd = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);
    if (fd < 0)
    {
        perror("fd open");
        return 1;
    }
    //int cnt = 5;
    //const char* buffer = "hello bit\n";
    //const char* buffer = "abcd";
    int n = 123456;
    //write(fd, buffer, strlen(buffer));
    //write(fd, &n, sizeof(n));
    char buffer[16];
    int num = snprintf(buffer, sizeof(buffer), "%d", n);
    buffer[num] = '\0';
    write(fd, buffer, strlen(buffer));
    //while (cnt--)
    //{
    //    write(fd, buffer, strlen(buffer));
    //}
    //printf("fd : %d\n", fd);
    close(fd);
    return 0;
}

运行并且使用vim打开log.txt

所以总结一下:

系统是不管你的写入方式的,语言层(如c,c++,java,py等)会提供自己的接口(如fwrite,iostream等)让你以你想要的方式写入

6.使用read函数

man 2 read查看定义

编写myfile.c代码:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    //写
    /*
    umask(0);
    //int fd = open("log.txt", O_CREAT | O_WRONLY, 0666);
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    //int fd = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);
    if (fd < 0)
    {
        perror("fd open");
        return 1;
    }
    //int cnt = 5;
    //const char* buffer = "hello bit\n";
    //const char* buffer = "abcd";
    int n = 123456;
    //write(fd, buffer, strlen(buffer));
    //write(fd, &n, sizeof(n));
    char buffer[16];
    int num = snprintf(buffer, sizeof(buffer), "%d", n);
    buffer[num] = '\0';
    write(fd, buffer, strlen(buffer));
    //while (cnt--)
    //{
    //    write(fd, buffer, strlen(buffer));
    //}
    //printf("fd : %d\n", fd);
    close(fd);
    return 0;
    */
    //读
    int fd = open("log.txt", O_RDONLY);
    if (fd < 0)
    {
        perror("fd open");
        return 1;
    }
    while (1)
    {
        char buffer[64];
        int n = read(fd, buffer, sizeof(buffer) - 1);
        if (n < 0) //读取失败
        {
            printf("读取失败!\n");
            return 1;
        }
        else if (n == 0) // n == 0 : 已到达 文件结束(EOF) 或 对端关闭写端且无数据可读
        {
            break;
        }
        else // n > 0 : 读取到n个字符
        {
            buffer[n] = '\0';
            printf("%s", buffer);
        }
    }
    close(fd);
    return 0;
}

这就是系统调用文件的基本接口啦,其实我们语言层的调用接口就是根据不同需求封装了不同的系统调用接口,实现不同功能,大家要好好掌握哦,下一篇博客我们将学习文件描述符:fd

相关推荐
Elastic 中国社区官方博客4 小时前
使用 Elastic Cloud Serverless 扩展批量索引
大数据·运维·数据库·elasticsearch·搜索引擎·云原生·serverless
超龄超能程序猿5 小时前
Docker GPU插件(NVIDIA Container Toolkit)安装
运维·docker·容器
Xの哲學5 小时前
Linux SMP 实现机制深度剖析
linux·服务器·网络·算法·边缘计算
2501_906150565 小时前
私有部署问卷系统操作实战记录-DWSurvey
java·运维·服务器·spring·开源
知识分享小能手5 小时前
Ubuntu入门学习教程,从入门到精通,Ubuntu 22.04的Linux网络配置(14)
linux·学习·ubuntu
钦拆大仁6 小时前
单点登录SSO登录你了解多少
服务器·sso
皇族崛起6 小时前
【视觉多模态】- scannet 数据的 Ubuntu 百度网盘全速下载
linux·ubuntu·3d建模·dubbo
岳来6 小时前
docker 从 Path 值看容器启动命令
运维·docker·容器
CAU界编程小白6 小时前
Linux系统编程系列之进程控制(下)
linux·进程控制
only火车头6 小时前
升级 ceph (16.2 -> 18.2) ceph mon 启动失败
服务器·ceph