【Linux】基础IO

基础IO

    学习IO相关操作, 系统调用接口, 文件描述符, 理解重定向功能,认识软硬链接, 对比区别.

标准库IO接口(stdio.h)

  接口描述

1. FILE *fopen(const char *pathname, const char *mode)

描述 :  以指定的方式打开一个文件

参数:

    pathname: 路径

    mode : 打开方式

        r : 以只读方式打开文件

        r+ : 以读写方式打开文件

       w : 以只写方式打开,不存在则创建 , 否则清空原有内容

       w+ : 以读写方式打开,不存在则创建, 并且清空原有内容

       a : 以追加写方式打开, 不存在则创建

      a+ : 以追加读写的方式打开, 不存在则创建

返回值:

    成功返回文件流指针, 失败返回NULL, 并且设置errno

2.  size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

描述 :  向文件中写入数据

参数:
     ptr : 要写入的字符串

    size : 写入的块大小

    nmemb : 写入的块个数

    stream : 文件流指针

3. size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

描述 :   从文件中读取数据

参数:

     ptr : 要读取的字符

    size : 读取的块大小

    nmemb : 读取的块个数

    stream : 文件流指针

4. int fseek(FILE *stream, long offset, int whence);

描述 :  从文件读写位置从whence开始偏移offset个字节

参数:

    stream :文件流指针

    offset : 偏移字节

    whence : 偏移起始位置

        SEEK_SET : 从文件起始位置开始偏移

        SEEK_CUR: 从文件当前位置开始偏移

        SEEK_END : 从文件末尾位置开始偏移

5. int fclose(FILE *stream);

描述 : 关闭打开的文件

参数 : 

    stream : 文件流指针

 操作句柄

    文件流指针 FILE*

标准库IO接口的使用

代码:

#include <stdio.h>
#include <string.h>

int main()
{
    FILE *fp = fopen("./csdn.txt","w+");
    if(fp == NULL)
    {
        perror("fopen error");
        return -1;
    }
     char buf[1024] = "my name is qujiale";
    fwrite(buf,strlen(buf),1,fp);
    fseek(fp,0,SEEK_SET);
    memset(buf,0x00,1024); 
    fread(buf,1024,1,fp);
    printf("buf:%s\n",buf);
    fclose(fp);
    return 0;
}

运行结果:

 

系统调用接口

接口描述

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

头文件 :

    #include <fcntl.h>

参数 : 

    pathname : 路径

    flags : 表明打开方式

        O_RDONLY : 以只读方式打开文件

        O_WRONLY : 以只写方式打开文件

        O_RDWR : 以读写方式打开文件

        O_CREAT : 如果文件不存在则创建

        O_TRUNC : 截断文件 (清空原有内容)

        O_APPEND : 以追加方式打开

    mode : 如果创建文件, 给出创建权限

返回值: 

    成功返回一个文件描述符, 失败返回-1

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

头文件 : 

    #include <unistd.h>

参数 : 

    fd : 文件描述符

    buf : 要写入的数据

    count : 要写入的数据长度

返回值 :

    成功返回实际写入的字节长度, 失败返回-1

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

头文件 : 

    #include <unistd.h>

参数 : 

    fd : 文件描述符

    buf : 要读取到的变量

    count : 要读取的子节数

返回值 :

    成功返回实际读取到的子节数, 失败返回-1

4. off_t lseek(int fd, off_t offset, int whence);

头文件 : #include <unistd.h>

同库函数接口

5. int close(int fd);

头文件 : #include <unistd.h>

同库函数接口

操作句柄 

    文件描述符

接口的使用

代码 : 

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

int main()
{
    umask(0);
    int fd = open("./syscsdn.txt", O_RDWR | O_CREAT, 0664);
    if(fd < 0)
    {
        perror("open error");
    }
    char buf[1024] = "my name is qujiale";
    int ret = write(fd, buf, strlen(buf));
    if(ret < 0)
    {
        perror("write error");
    }
    lseek(fd, 0, SEEK_SET);
    memset(buf, 0x00, sizeof(buf));
    ret = read(fd, buf, 1024);
    if(ret < 0)
    {
        perror("read error");
    }
    printf("buf:%s\n",buf);
    fclose(fd);
    return 0;
}

运行结果 : 

 

库函数和系统调用接口

    库函数 : fopen, fwrite, fseek, fclose, fread

    系统调用接口 : open, write, seek, close, read.

我们来看一张图 :

    我们很明显的可以看到库函数和系统调用属于上下级的调用关系, 库函数最终还是要调用系统调用接口, 我们可以认为库函数就是对系统调用接口的一层封装, 方便我们二次开发.

  

 

文件描述符

概念

   进程通过struct file结构体来描述打开的文件--使用了struct file *fd_array[] , 文件描述符就是这数组的下标--用户打开文件,操作系统通过file结构体描述文件 , 并将指针添加进入fd_array[]中 , 向用户返回这个文件描述信息在数组中的位置(下标) , 用户操作文件的时候,将这个下标传递给操作系统 , 操作系统通过下标找到文件描述信息进而操作文件, 所以其实文件描述符就是一个从0 开始的整数

分配原则 

    最小未使用原则

    默认从3开始, 因为一个进程启动之后, 默认会打开三个文件

标准输入标准输出标准错误 
012文件描述符

STDIN_FILENO

STDOUT_FILENOSTDERR_FILENO 
stdinstdoutstderr文件流指针
   0,1,2 会默认占用, 所以是从3开始的

 

文件流指针

1. 文件流指针中包含了文件描述符这个成员变量

2. 文件流指针结构体中描述了一个缓冲区, 我们叫做用户态缓冲区

    如果我们 exit() 退出一个进程的时候, 这个时候会刷新一下缓冲区, 将内容打印到标准输出中, 也就是显示器

    如果我们_exit()  退出一个进程的时候, 这个时候不会刷新缓冲区, 因为_exit()是系统调用接口, 没有缓冲区来打印.

 

文件描述符和文件流指针的关系

关系

    文件流指针 : 库函数的操作句柄

    文件描述符 : 系统调用接口的操作句柄   

    文件描述符实际上是文件流指针FILE*结构体的一个成员变量 fileno

操作文件的具体流程

    通过文件流指针操作文件的时候其实就是通过文件流指针FILE* fp 找到文件描述符 int fd, pcb中有一个结构体指针指向file_struct, 这个结构体中的array数组中有每个文件对应的信息, 我们通过fd找到array数组中fd下标对应的文件信息, 进而再去操作文件, 值得注意的是, 文件流指针是在用户空间, 但是文件描述符所对应的文件信息都在内核里面, 文件流指针只是封装了文件描述符. 所以我们发起系统调用之后相当于从用户态切换到了内核态

写入文件

    写入文件流指针fp(FILE*)所包含的缓冲区(用户态缓冲区)中 -> 刷新缓冲区/缓冲区满了 ->通过fp中的成员变量文件描述符fd找到文件的描述信息(file_struct的array数组中) -> 然后写入到文件

进程从用户态切换到内核态的方法

    发起系统调用

 

重定向

概念:

  针对文件描述符进行重定向, 改变文件描述符这个下标所对应的文件描述信息,操作相同的描述符,但是具体操作的文件已经改变

  我们先来看一段代码

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    close(1);
    int fd = open("./csdn.txt", O_RDWR);
    if(fd < 0)
    {
        perror("open error");
    }

    printf("fd:%d\n",fd);
    close(fd);
    return 0;
}

    我们关闭了标准输出, 然后创建fd, 这时候我们运行程序, 应该是输出一个1, 因为要遵循最小未使用原则, 但是我们来看运行结果

  可以看到什么也没有打印, 那么这是为什么呢?

     因为标准输出文件现在已经被关闭了.所以现在打印不出来了. 现在1号描述符对应的不是显示器文件了, 而是我们写的csdn.txt文件, 所以这个时候应该将数据写到csdn.txt了, 那我们来看一下csdn.txt

我们能看到, 还是什么都没有, 这又是为什么呢?

     因为\n的作用不仅仅是换行, 并且如果当前操作的文件是标准输出文件, 则换行还可以刷新缓冲区, 但是如果当前操作的文件不是显示器(一个特殊文件), 那么\n就只有换行的功能, 并没有刷新缓冲区的功能. 并且close()是一个系统调用接口, 不具备(用户态)缓冲区, 所以更不会刷新缓冲区, 所以这个时候我们要手动刷新缓冲区.

    在close(fd)之前加上fflush(stdout);

再次运行:

    可以看到, 这次我们就成功的把这句话写进去了

    我们发现,本来应该输出到显示器上的内容,输出到了文件 csdn.txt 当中,其中,fd=1。这种现象叫做输出重定向
 

本质

    既然我们知道了的上面这种现象叫做重定向, 那么重定向的本质是什么呢?

       重定向指的是描述符的重定向,描述符并没有改变, 改变的是描述符对应的文件信息

 dup2系统调用

    int dup2(int oldfd, int newfd);

        让newfd 也指向 oldfd 所指向的文件, 并且如果newfd本身已经打开了文件, 则关闭原来的文件, newfd 和 oldfd  最终都指向oldfd所对应的文件

    示例 :

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    int fd = open("./csdn.txt", O_RDWR);
    if(fd < 0)
    {
        perror("open error");
    }

    //打印之前进行重定向
    dup2(fd,1);
    
    printf("fd:%d\n",fd);
    fflush(stdout);
    close(fd);
    return 0;
}

运行结果:

这个时候printf打印, 就把fd等于3写入到csdn.txt中了

 

文件系统

定义 : 磁盘上的文件管理系统

    除了交换分区之外, 每一个磁盘分区都有一个文件系统.

在文件系统中是怎样管理文件的呢,

    以linux下的ext2文件系统为例: 

        将磁盘分成很多个数据块, 每一块的大小是4K

划分:

    inode节点 : 每个文件都有一个inode节点,  描述文件的信息, 比如 : 大小/ 权限/ 时间/ 占用块个数/ 链接数/ 数据地址

    data : 文件的具体数据

    inode_bitmap : 标记哪些inode节点使用了, 哪些inode节点没有使用

    data_bitmap :  数据块位图区域, 标记哪些数据块已经使用了, 哪些还没有使用, 是一个位图

    超级块 :  记录文件系统信息, inode节点有多少, 数据块有多少个等等, 记录文件系统的统筹信息.

文件的存储/读取过程

文件的存储过程:
   
通过inode_bitmap在inode区域获取空闲inode节点,通过data_bitmap获取空闲数据块在inode节点中记录文件信息以及数据块位置,并且将文件数据写入到数据块中,将自己的目录项信息添加到所在目录文件中

    目录项 : 文件名 + inode节点号

文件的读取过程:
   
cat a.txt,在目录项文件中通过文件名获取文件inode节点号(文件唯一),通过inode节点号在inode区域中找到inode节点,通过inode节点中的的数据块地址信息在指定数据块读取数据

软链接/硬链接文件

概念:

    软链接文件: 就像是一个文件的快捷方式,是一个独立的文件, 存放源文件的路径, 操作软链接会通过这个路径找目录项

    硬链接文件: 一个文件的名字(目录项), 与原文件公用同一个inode节点

 

如何创建 : 

    软链接文件 : ln -s tmp.txt tmp.soft

    硬链接文件: ln tmp.txt tmp.hard

 

区别 :
    1.  删除原文件, 软链接文件将失效, 硬链接无影响(链接数-1)

    2.  软链接可以跨分区创建, 硬链接不可以 ,  因为硬链接针对inode节点号, 每个分区的文件系统都有可能不同, 有可能就没有inode节点,  就算两个分区文件系统是一样的, 也有可能造成inode节点冲突, 所以硬链接不能跨分区. 

    3. 软链接可以对目录创建 , 硬链接不可以 ,  因为在linux下, 只有一个目录结构, 目录本来就是跨分区的, 但是硬链接不能跨分区, 所以也不能对目录创建, 本质上还是因为inode节点的原因.

    4. 软链接针对的是目录项, 硬链接针对的是inode节点

 

联系 : 

    对软链接和硬链接进行操作, 源文件均会改变

附加:

    我们ls -l 看到的文件信息都是inode节点里面建立的, 以及stat

以上就是IO相关的一些知识点, 感谢观看^_^

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值