0


【Linux】浅谈文件原理与操作

问题引入

🌸以前我们学过C语言的文件操作,而不同语言的文件操作都是不一样的,我们该如何理解这一现象,能不能用一种统一的视角看待所有文件操作?今天就一起来谈谈文件操作。


浅谈文件原理

我们都知道,文件 = 内容 + 属性,对于一个文件的操作可以看成对内容或属性的操作。

通过冯诺依曼体系我们知道当我们对文件进行操作时,该文件一定处于内存之中,而未被进行操作的文件则位于磁盘里。

在操作系统看来,文件便可以被分成两大类:磁盘文件被打开文件

🌸其中被打开文件至少需要将文件属性加载到内存之中,而每次文件操作前我们都要先打开文件的本质就是:将目标文件属性加载到内存中。因此,OS内部一定会同时存在大量的被打开的文件。需要将其以先描述再组织的方式管理起来

*这样在OS内部对于打开文件的管理就转换成了对链表的增删查改。即文件被打开前,OS要为被打开文件创建内核数据结构 struct file*

🌸那么文件是被谁打开的呢?

  • 用户通过调用进程完成打开文件的操作

如此解释后,我们对于文件又有进一步的认识,下面我们来介绍一下在OS层面文件操作的系统接口。

文件操作

🌸对C语言文件操作不熟悉或有些忘记了的,也可以看看这篇复习一下:C语言文件操作的基础应用。

文件的打开与关闭

open

🌸通过手册查询,我们可以找到 open 函数的相关信息,其中的参数分别是文件路径打开文件的选项

🌸其中的 flag ,我们不能将其看成一个整数而是将其看作一个位图

🌸其中每一个位置都代表一个选项,为 1 则表示选中反之不选,库中定义了宏方便我们直接使用。

  • O_WRONLY 只写选项
  • O_RDONLY 只读选项
  • O_APPEND 追加写入
  • O_TRUNC 打开时清空文件
  • O_CREAT 若文件不存在则创建
  • O_RDWR 支持同时读写

🌸在使用时使用 | 操作符标记位图的对应位置即可。

int main()
{
    open("log.txt", O_WRONLY | O_TRUNC | O_CREAT);  //以只读每次打开清空文件若不存在则创建新文件的方式打开
    return 0;
}

🌸这时候,我们便会发现现在的这个操作跟 fopen 十分的相似,只不过将 w 选项拆成了三个选项。其实,fopen 的底层便是调用了这个系统调用进行文件的打开操作。

🌸虽然我们成功创建出一个文件了,但此时我们可以看到,该文件的权限却是乱码,因此在创建文件时我们就需要手动设置文件的权限。

🌸open 还重载了三个参数的版本,供我们设置文件权限

int main()
{
    open("log.txt", O_WRONLY | O_TRUNC | O_CREAT,0666);
    return 0;
}

🌸这下创建出来的就是正常权限的文件了,但是却与我们设置的 0666 不一样而是 0664 呢?这是由于文件的权限还受到权限掩码的影响,因此需要将 umask 置零。

int main()
{
    umask(0);
    open("log.txt", O_WRONLY | O_TRUNC | O_CREAT,0666);
    return 0;
}

🌸这下就根据我们要求创建出对应权限的文件了。

🌸而 open 有一个返回值,我们刚才忽略掉了,其为文件描述符又名 fd,我们需要定义一个变量接收一下它,之后需要用它定位文件。

🌸若 fd 为 -1 说明打开文件失败,我们可以在打开文件后增加一个判断。

int main()
{
    umask(0);
    int fd = open("log.txt", O_WRONLY | O_TRUNC | O_CREAT,0666);
    if(fd == -1)
    {
        printf("fd: %d,errstring is: %s\n",fd,strerror(errno));
    }
    return 0;
}

close

🌸关闭文件就需要我们上面存起来的 fd 了,直接传入 fd 即可关闭文件。

int main()
{
    umask(0);
    int fd = open("log.txt", O_WRONLY | O_TRUNC | O_CREAT,0666);
    if(fd == -1)
    {
        printf("fd: %d,errstring is: %s\n",fd,strerror(errno));
    }
    close(fd);
    return 0;
}

write与read

🌸打开文件后我们就可以根据需求对文件进行读写了,先介绍 write 接口。

*🌸参数分别是 fd写入内容的字符串写入的字节数,写入后返回写入的字节数,失败则返回 -1。*

🌸这里我先将用 snprintf 内容格式化写入一个字符串中,再写到文件里。

int main()
{
    umask(0);
    int fd = open("log.txt", O_WRONLY | O_TRUNC | O_CREAT,0666);
    if(fd == -1)
    {
        printf("fd: %d,errstring is: %s\n",fd,strerror(errno));
    }
    const char* ptr = "helloworld";       //原字符串
    char str[20];                         //转接的字符数组
    int i = 1;
    snprintf(str,sizeof(str),"%s: %d",ptr,i);   //加个序号格式化写入
    write(fd,str,strlen(str));                  //写入文件
    close(fd);                                  //关闭文件
    return 0;
}

[注意]:****\0 是C语言库中的设定,直接写入到文件中会被识别成乱码,使用 write 写入时要注意不写入 \0。因此写入时直接使用 strlen 便可以自动规避掉 \0。

**🌸之后我们来看读取的操作,需要传入 fd 定位文件,之后将文件内的数据读入数组中,读取 ****count **个字节。

🌸成功读取后会返回读取的字节数,若读取失败则会返回 -1。

🌸这次我们以读取的方式打开文件,由于不用创建文件因此就不需要手动设置文件权限了。

int main()
{
    int fd = open("log.txt", O_RDONLY);     //只读打开文件
    char buffer[1024];
    size_t n = read(fd, buffer, sizeof(buffer) - 1);   //为\0留一个位置
    if (n > 0)      //说明读入数据
    {
        buffer[n] = '\0';            //在数据尾部插入\0
        printf("%s\n", buffer);      //打印出数据内容
    }
    return 0;
}

🌸同时为了方便接下来的读写,读取需要为 \0 预留一个位置,之后再加上即可。

再谈C库文件操作

🌸在上面我们就说过C库文件操作的底层本质上也是调用了操作系统提供的系统调用,即库函数只是对系统调用的封装

🌸那又是为什么呢?我们可以用下面这张图简单理清几者之间的关系。

🌸只要涉及文件操作,就与文件读写分不开关系,在读写的过程中便会涉及到对硬件的访问,而硬件是由操作系统进行管理的。因此从用户层面出发,只要访问到硬件就无法绕开操作系统。

🌸不仅只是C语言,任何语言的文件操作都是对系统调用的封装,都是借助系统接口才能够实现!!!


🌸好了,今天 浅谈文件原理与操作 的相关内容到这里就结束了,如果这篇文章对你有用的话还请留下你的三连加关注。

标签: linux 运维 服务器

本文转载自: https://blog.csdn.net/Lin_Alpaca/article/details/131144445
版权归原作者 LinAlpaca 所有, 如有侵权,请联系我们删除。

“【Linux】浅谈文件原理与操作”的评论:

还没有评论