Linux 基础I/O

  • Post author:
  • Post category:linux





Linux文件操作

系统调用接口:



1.open

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

int open(const char* pathname,int flags);

int open(const char* pathname,int flags,mode_t mode);


pathname: 要打开或创建的文件



flags: 打开文件时,可以传入多个选项,用下面的一个或多个常量进行或运算,构成flags

O_RDONLY: 只读打开

O_WRONLY: 只写打开

O_RDWR: 读写打开

上述三个常量,必须指定一个且只能指定一个

O_CREAT:若文件不存在,则创建

O_APPEND: 追加写

O_TRUNC: 若文件存在,并且以只写/只读打开,则将清空文件内容

flags参数都是宏,这些标记为其实都是通过位图来传递的。

返回值是文件描述fd,稍后再说



2. read

#include<unistd.h>

ssize_t read(int fd,void *buf,size_t count);

在这里插入图片描述

在这里插入图片描述

#include<unistd.h>

ssize_t write(int fd,void* buf,size_t count);

在这里插入图片描述



文件描述符 fd

void test1()
{
        umask(0);
        int fd3 = open("love.txt",O_RDONLY|O_CREAT|O_APPEND,0666);
        printf("%d\n",fd3);

        char buffer[128];
        ssize_t s = read(fd3,buffer,sizeof(buffer)-1);
        if(s>0)
        {
                buffer[s] = '\0';
                printf("%s\n",buffer);
                printf("%d\n",s);
        }


        close(fd3);

}

当我们打开第一个文件的时候,运行会发现fd 为3 然后再打开多个文件,会发现fd依次为4,5,6,7,…

为什么会有这种现象呢?为什么从3开始呢?


Linux进程默认情况下会有三个缺省打开的文件描述符,分别是:

标准输入: 0 (键盘)

标准输入: 1 (显示器)

标准错误: 1 (显示器)

Linux下:一切皆为文件,这是Linux的哲学。

当文件没有被打开的时候,文件在磁盘中,但当一个文件被打开后,文件会被读取到内存中,一个进程可以打开多个文件,而这些打开的文件操作系统需要很好的管理,所以一个文件打开,会先创建对应的内核数据结构,而操作系统会把一个进程打开的所有文件通过链表连接起来,这样对打开文件的管理变成了对链表的增删查改,

strcut file

{

}

在这里插入图片描述


文件的重定向

#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>


int main()
{

        close(1);
        int fd = open("my.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
        printf("fd: %d\n",fd);

        close(fd);



        return 0;

}


看上面的代码,运行会发现没有任何的打印,这是为什么呢? 因为我们close(1),相当于把标准输出流(stdout)关闭了,这时候操作系统会遍历 fd_array[]数组,发现数组下标为1的 file

是空的,这时候操作系统就会自动把1号下标的file

指向我们新打开的文件,这就是重定向的本质!!

在这里插入图片描述

但是这时候既然重定向了,那么说明printf的内容应该会输出到 my.txt的文件中,但是我们打开my.txt文件却发现是空的,这是为什么呢? 这里先卖个关子,只要加上 fflush(stdout) 刷新缓冲区,my.txt里面就有东西了,等下我们将缓冲区的时候再说这个,我们先把重定向讲完:

关于重定向,在这里我们介绍操作系统提供的接口: dup2

在这里插入图片描述

在这里插入图片描述

int dup2(int oldfd,int newfd);


dup2函数可以让 文件描述符为newfd变为文件描述符为oldfd的一份拷贝,也就是说,newfd会重定向为 oldfd 调用完函数后,只剩下oldfd了

若dup2调用成功则返回新的文件描述符,出错则返回-1.

#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>


int main()
{

        //close(1);
//      int fd = open("my.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
//      printf("fd: %d\n",fd);
//      
//      fflush(stdout);
//      close(fd);



        int fd = open("my.txt",O_RDONLY);
        int dp2 = dup2(fd,0);

        if(dp2 < 0 )
        {
                perror("DUP2");
                return 1;
        }

        char arr[128];
        read(0,arr,sizeof(arr));
        fprintf(stdout,"%s",arr);




        return 0;

}




缓冲区的理解:

什么是缓冲区?怎么理解?


1. 缓冲区的本质就是一段内存

2. 缓冲区的存在能够解放进程的时间,缓冲区集中处理数据刷新以达到减少IO次数,提高整机的效率的目的

缓冲区在哪里?

FILE的结构体里面有对应的缓冲区,而该FILE对应的是语言级别的缓冲区,也就是说每一个文件都有一个专属的fd和他的语言级别的缓冲区。

下面介绍关于刷新策略的问题:

  1. 无缓冲(立即刷新) :a. 进程退出 b. 用户强制刷新
  2. 行缓冲 (逐行刷新) : 显示器文件
  3. 全缓冲 (缓冲区满才刷新):块设备对应的文件(磁盘文件)

一个奇怪的问题:

在这里插入图片描述

观察上述代码,当我们执行代码并将其重定向到一个文件中,会发生什么呢?

在这里插入图片描述

为什么会有这样的情况呢????


首先,当我们重定向到一个文件时,缓冲区的刷新策略变为了全刷新,也就是缓冲区满,或者进程结束刷新。其次,我们所说的这些缓冲区都是语言级别的缓冲区,而write作为系统调用是没有缓存区的,像printf,fputs fprintf这些函数底层实际上是封装了write并且提供了缓冲区。因此刚开始的printf,fputs,fprintf都只是先写到缓冲区中,并不会直接写到文件中,而write直接写到文件中,那么为什么printf,fputs,fprintf会输出了两次呢?? 我们知道,当我们fork后,会产生子进程和父进程,缓冲区是FILE内部来维护,也就是说是父进程内部的数据区域,当fork时,父子进程会先共享这段数据段,当子进程要结束时,会刷新缓冲区,将缓冲区的内容刷新到文件中并且清空缓冲区,但此时父进程会立刻发生写时拷贝,保留原缓冲区的内容,当父进程结束时,会再刷新一次缓冲区,因此就会刷新两次!




磁盘的存储结构

在这里插入图片描述

当进行磁盘的读写时,磁头通过对磁盘的某一个面,某一个磁道的某一个扇区对磁盘进行定位,也就是CHS地址(C:Cylinders),(H:heads),(S:Sector)

在这里插入图片描述

而在操作系统中,可以把磁盘看成一个线性的数组,可以用LBA地址进行磁盘的定位。


注意:磁盘的一个扇区的大小是512字节,但是文件系统进行磁盘的访问的基本单位是4kb 也就是8个扇区,为什么这样设计呢? 这样做有两个目的:

  1. 提高 I/O效率
  2. 有助于解耦合

操作系统通过对磁盘分块,分组进行分治的管理,这样只要管理好部分就可以管理好全部了。

在这里插入图片描述

下面介绍每个磁盘文件系统:

Boot Block: 启动块,大小确定

Data blocks:以块为单位,进行文件数据的保存

inode Table:以128字节为单位,进行inode属性的保存(一般而言,一个文件对应一个独一无二的indoe编号)

struct inode

{


int id;

mode_t mode;

user name;

data d;

}(保存文件属性)

![在这里插入图片描述](https://img-blog.csdnimg.cn/e13be14a6cd64f118eaca931be0281c1.png#pic_center

Block Bitmap:位图,用于判断某个Block group是否已经被占用

indoe Bitmap:位图,用于判断某个inode块是否被占用

Group Descriptor Table:有多少inode,起始的inode编号,有多少个inode被使用,有多少block被使用,还剩多少,总group大小是多少等信息

Super Block:文件系统的顶层数据结构 存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了


我们知道,目录也是文件,那么文件就应该有它对应的 内容 + 属性,目录的属性应该就是它的inode的结构体,那目录的内容是什么呢?


目录的内容其实就是目录存储了该目录下的所有文件和其inode之间的一个映射关系

那么当我们创建一个文件时,操作系统都干了什么呢?

创建文件名,分配inode编号,找到该文件所处的目录,根据目录的inode,找到目录的data block,将文件名和inode的映射关系写到data block中


软硬链接:

  1. 软链接

软链接是一个独立的文件,有自己独立的inode编号,相当于Windows下的快捷方式,软链接的文件内容保存的是指向原文件的路径。

在这里插入图片描述

ln -s来创建软链接,unlink进行删除软链接

  1. 硬链接

硬连接并不是一个独立的文件,它和目标文件使用的是同一个inode,相当于在目录中添加一个新的文件名和inode编号的映射关系,而硬连接数,就相当于inode属性中的一个计数器,就是有几个文件名和inode建立了映射关系。



版权声明:本文为weixin_62688907原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。