系统IO

引言:

我们之前已经学过了C的IO流,这两者语言层面的IO相较于系统的IP流是较为上层的,C中的FILE*就封装有系统底层的文件操作,今天就让我们学习linux系统层面的输入输出流,从底层开始认识基础IO。

系统文件I/O

  • 写入文件:
#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("test", O_WRONLY|O_CREAT, 0644);

    int count = 10;
    const char* str = "hellow ALA\n";

    while(count--)
    {
        write(fd, str, strlen(str));
    }
    close(fd);
    return 0;
}
  • 读出文件:
#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("test", O_RDONLY);

    char buff[1024];
    const char* str = "hellow ALA\n";

    while(1)
    {
        ssize_t s = read(fd, buff, strlen(str));
        if(s > 0)
        {
            printf("%s", buff);
        }
        else
            break;

    }
    close(fd);
    return 0;
}
  • 结果:
    在这里插入图片描述

  • open接口介绍:
    在这里插入图片描述
    返回值:成功,返回新的文件描述符,失败,返回-1

    参数:

    • pathname:要打开的目标文件
    • flags:打开文件时,传入多个选项,设置为只读/只写/读写等
      • O_RDONLY:只读打开
      • O_WRONLY:只写打开
      • O_RDWR:读,写打开。

文件描述符fd

  • 从open函数我们知道了,文件描述符可以认为是一个小整数。

  • 0&1&2

    • 每个文件运行结束时,默认打开三个字节流:
    • stdin(标准输入,键盘)
    • stdout(标准输出,显示器)
    • stderr(标准错误,显示器)
      让我们尝试着使用这三个字节流:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>

int main()
{
    
    const char* str = "hellow ALA\n";
    write(1, str, strlen(str));
    write(2, str, strlen(str));
    return 0;
}

结果:通过write在显示器上成功将hellow ALA输出
在这里插入图片描述

  • 文件与文件描述符的映射关系:
    在这里插入图片描述
    解释:要管理文件描述符,就必须先描述再组织。用文件描述符数组(fd_array[])将其描述起来,在通过files_struct将其组织起来。
    本质:文件描述符的本质实际上就是数组下标。
文件描述符的分配:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>

int main()
{
    int fd0 = open("test0.txt", O_WRONLY|O_CREAT, 0644);
    int fd1 = open("test1.txt", O_WRONLY|O_CREAT, 0644);
    int fd2 = open("test2.txt", O_WRONLY|O_CREAT, 0644);
    int fd3 = open("test3.txt", O_WRONLY|O_CREAT, 0644);
    int fd4 = open("test4.txt", O_WRONLY|O_CREAT, 0644);
    
    printf("fd0:%d\n", fd0);
    printf("fd1:%d\n", fd1);
    printf("fd2:%d\n", fd2);
    printf("fd3:%d\n", fd3);
    printf("fd4:%d\n", fd4);
    
    close(fd0);
    close(fd1);
    close(fd2);
    close(fd3);
    close(fd4);

    return 0;
}

结果:
在这里插入图片描述
可以看到,除了前三个系统默认打开的描述符之外,其他的文件的文件描述符依次递增。

重定向

原理:
“<”输入重定向,关掉stdin(0),从文件中读取数据
“>”输出重定向,只重定向一号描述符,以写入的方式,写到指定文件中
“>>”追加重定向,将显示器中的数据,以追加的形式写到文件末尾
尝试实现重定向:

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

int main()
{
    close(1);
    int fd0 = open("test0.txt", O_WRONLY|O_CREAT, 0644);
    int fd1 = open("test1.txt", O_WRONLY|O_CREAT, 0644);
    int fd2 = open("test2.txt", O_WRONLY|O_CREAT, 0644);
    int fd3 = open("test3.txt", O_WRONLY|O_CREAT, 0644);
    int fd4 = open("test4.txt", O_WRONLY|O_CREAT, 0644);
    
    printf("fd0:%d\n", fd0);
    printf("fd1:%d\n", fd1);
    printf("fd2:%d\n", fd2);
    printf("fd3:%d\n", fd3);
    printf("fd4:%d\n", fd4);
    fflush(stdout); 
    close(fd0);
    close(fd1);
    close(fd2);
    close(fd3);
    close(fd4);

    return 0;
}

结果:在显示器上没有输出,输出结果全部打到了text0.txt中。
在这里插入图片描述
在这里插入图片描述
原因:关闭标准输入后,test0.text文件占用了1号文件描述符,原本要写在显示器的输出,全部写入了test0.tex这个文件中。

示意图:
在这里插入图片描述

  • dup2系统调用:
    #include<unistd.h>
    int dup2(int oldfd, int newfd);
    
    • 作用:让newfd称为oldfd的一个拷贝。
FILE:
  • 原理:由于IO相关函数与系统调用接口对应,并且库函数封装系统调用,因此两者是上下级关系,因此从本质上来说,访问文件都是通过fd访问的。FILE*中必定封装了fd(文件描述符)。

  • 举例:
    fwrite通过“FILE*”(文件指针,对file_struct的封装),再从file_struct中将fd_array[](文件描述符数组找到),通过linux底层调用write操作,在对应的文件描述符中进行“写”操作。

让我们看看下面这段代码:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
int main()
{
    const char* str0 = "hellow printf\n";
    const char* str1 = "hellow fwrite\n";
    const char* str2 = "hellow write\n";
    
    printf("%s", str0);
    fwrite(str1, strlen(str1), 1, stdout);
    write(1, str2, strlen(str2));

    fork();
    return 0;


}

正常运行的结果:
在这里插入图片描述
但是如果将导入文件中的话:在这里插入图片描述
我们发现Printf和fwrite(库函数)都输出了两次,而write只输出了一次,为什么?

  • 1.因为fwrite和printf作为库函数,都带有缓冲区,当发生重定向到文件中,缓冲方式会从行缓冲转换为全缓冲。
  • 2.放在缓冲区的数据不会立即刷新,只会在进程退出后刷新,写入文件中
  • 3.但是fork之后,因为父子数据会发生写时拷贝,因此父进程刷新时,子进程也有了同样的数据,产生了一样的代码
  • 4.然而write不会刷新,说明其没有缓冲区。

理解文件系统

当我们使用ls -l的时候,不仅看到了文件名,还看到了文件属性

在这里插入图片描述
ls -l的工作原理:

  • 当输入ls -l之后,程序变成了进程,进程通过操作系统访问硬盘中的文件,文件分为两个部分:文件信息(属性)和文件内容。操作系统通过访问文件属性,再将文件属性打印到电脑显示器上实现功能。

  • 下面让我们来看看文件属性(inode)是什么样的。
    我们先通过stat查看文件属性
    在这里插入图片描述
    可以通过ls -i查看Inode号:在这里插入图片描述

文件具体分为5个部分:
在这里插入图片描述

  • 1.超级块:存放文件系统本身的结构信息。
  • 2.inode位图:每个bit表示一个inode是否可以使用
  • 3.inode区:用来存放inode信息
  • 4.数据位图:每个bit表示一个位置的数据区能否使用
  • 5.数据区:用来存放数据
但是我们在日常操作中,看不到Inode号,我们是如何使用它的呢?

由于Linux下一切皆文件,所以目录也是文件,目录存放inode和文件名的映射关系,并通过目录维护inode和文件名之间的映射关系。

因此我们可以更系统的解释ls -l是如何实现的

linux一切皆文件 -> 目录是文件->目录中维护着inode和文件名的映射关系->操作系统依靠打开目录,提出映射关系,经过inode把属性打印在显示器上。

创建文件:
  • 1.找inode位图中为0的位置
  • 2.由0->1,往inode区对应的位置放入属性,分配inode号
  • 3.将块位图中为0的位置
  • 4.由0->1,然后在块区中对应的位置放入数据。
查文件:
  • 1.查到inode号
  • 2.在inode位图中找到对应的Inode编号
  • 3.找到对应的数据,从当前数据开始读数据大小长度的数据块到磁盘中
删除文件:
  • 1.Inode位图由1->0
  • 2.对应块位图由1->0
  • 3.将目录中文件名与Inode的映射关系去除
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值