Linux —— 基础IO(1)

简介: Linux —— 基础IO(1)

前言

     我们之前都有学过文件操作相关的函数,能够利用C语言相关的库函数进行文件的写入和读取;我们只是会用相关的库函数接口,但是并不知道文件究竟是怎么被写入的,怎么被读取的,文件操作的底层原理究竟是什么一概不知,本篇博客将会详细介绍文件操作的底层原理。让我们对文件操作有一个新的认识。


接下来将会从一下几点入手,带大家深入的理解文件操作:


复习C文件IO相关操作;

认识文件相关系统调用接口;

认识文件描述符,理解重定向;

对比fd和FILE,理解系统调用和库函数的关系;

理解文件系统中inode的概念

认识软硬链接,对比区别

认识动态静态库,学会结合gcc选项,制作动静态库

————————————————

版权声明:本文为CSDN博主「霄沫凡」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/sjsjnsjnn/article/details/125714967

一、复习C文件的IO相关操作

       我们简单的复习一下C文件的操作,以写文件和对文件为例,其他相关的操作可以去看这篇博客:【C语言】—— 文件操作(详解)

1.C语言中相关的文件操作接口介绍

image.png

2.C语言中的写文件

1ecd1b2606ed46e9956a89f231c9802c.png

2020062310470442.png

3.C语言中的读文件

1ecd1b2606ed46e9956a89f231c9802c.png

2020062310470442.png

4.C程序默认打开的三个输入输出流

    以上我们简单演示了C语言的文件操作,但这只是停留在语言层面上,简单的会使用还是不够的,很难对文件有一个比较深刻的理解。我们都知道C程序会打开三个默认输入输出流:

extern FILE *stdin;   //标准输入 --- 所对应的是键盘
extern FILE *stdout;  //标准输出 --- 所对应的是显示器
extern FILE *stderr;  //标准错误 --- 所对应的是显示器

我们刚刚在使用fputs函数向文件写入数据,而文件的类型和这里默认打卡的三个流的类型是一样的,那么我们也可以直接向显示器写入数据:

1ecd1b2606ed46e9956a89f231c9802c.png

2020062310470442.png

fputs能够像一般文件或者是硬件写入数据,我们就可以理解为一切皆文件;(后面做解释)

二、系统文件IO

       我们无论是写文件还是读文件,文件都是来源于硬件(磁盘...),硬件是由操作系统管理的;用户不能够直接跳过操作系统将文件写入,必须贯穿整个操作系统。那么访问操作系统就需要调用系统接口来实现文件向硬件写入的操作;也就是你在C语言或其他语言上使用的文件相关库函数,其底层都是要调用系统接口的;如下图所示:

1ecd1b2606ed46e9956a89f231c9802c.png

1. open

系统接口使用open函数打开文件,其所需的头文件有三个。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags, mode_t mode);

1.open的参数

参数:


1.pathname:你要打开的文件路径,如果没有会自动创建


2.flags:以什么样的形式打开:


O_RDONLY   ---  以只读的方式打开文件

O_WRNOLY  ---  以只写的方式打开文件

O_APPEND   ---  以追加的方式打开文件

O_RDWR       ---  以读写的方式打开文件

O_CREAT      ---  当目标文件不存在时,创建文件

3.mode:表示创建文件时的权限设置


1ecd1b2606ed46e9956a89f231c9802c.png

2020062310470442.png

        在上述代码中的第二个参数需要解释一下,我们刚刚已经了解了第二个参数是以什么样的形式打开文件,它是操作系统在用户层面上给内核层传递的标志位。flags的类型是int,他就有32个比特位,一个比特位就可以代表一个标志位,如果两个或起来就可以传递多个标志位。操作系统内部在进行按位与运算,判断那个位被设置了1或0,从而对文件打开方式进行设置。

1ecd1b2606ed46e9956a89f231c9802c.png

实际上传入flags的每一个选项在系统当中都是以宏的方式进行定义的:

1ecd1b2606ed46e9956a89f231c9802c.png

2.open的返回值

open的返回值是fd——文件描述符 (文件打开成功,返回对应的fd值,打开失败,返回的是-1)

1ecd1b2606ed46e9956a89f231c9802c.png

从下图的运行结果发现,文件描述符从3开始,依次递增,有点像数组的下标;那么0/1/2是什么呢?这里没有从0开始,其实,是将0/1/2分配给了三个流:0---标准输入、1---标准输出、2---标准错误

1ecd1b2606ed46e9956a89f231c9802c.png

2.close

关闭文件描述符:

int close(int fd);

关闭文件只需要传入你想要关闭的文件描述符即可;

3.write

向文件描述符写入数据:

1. #include <unistd.h>
2. ssize_t write(int fd, const void *buf, size_t count);

参数解读:将buf中的数据写入fd,写入count个

#include <stdio.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <unistd.h>  
#include <string.h>  
int main()  
{  
     int fd = open("log1.txt", O_WRONLY | O_CREAT, 0644);  
     if(fd < 0){  
        perror("open");  
        return 1;  
     }  
     const char* msg = "hello linux!\n";  
     int count = 5;  
     while(count--){  
         write(fd, msg, strlen(msg));
     }
     close(fd);
     return 0; 
}

1ecd1b2606ed46e9956a89f231c9802c.png

4.read

从文件描述符读数据:

ssize_t read(int fd, void *buf, size_t count);
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
    int fd = open("./log1.txt",O_RDONLY);
    if(fd < 0){
        perror("open");
        return -1;
    }
    char buffer[128];
    ssize_t ss = read(fd, buffer, sizeof(buffer) - 1);
    if(ss > 0){
        buffer[ss] = 0;
    }
    close(fd);
    printf("%s\n",buffer);   
    return 0;                                                                                                           
}

1ecd1b2606ed46e9956a89f231c9802c.png

三、文件描述符fd

       一个进程可以打开多个文件,当操作系统中存在大量的文件时,操作系统就需要对这些文件进行管理,在内核当中,管理这些已经打开的文件就要设计对应的结构体,把打开的文件描述起来,我们把描述文件的结构体称为(struct file),然后将这些结构体以双链表的形式链接起来,便于管理。

  多个进程和多个文件在操作系统中,是如何区分哪一个文件属于哪一个进程的呢?


操作系统为了能够让进程和文件之间产生关系,进程在内核当中包含了一个结构 struct  files_struct,这个结构中又包含了一个数组结构struct  file* fd_array[ ] 在task_struct的PCB当中又包含了一个指针 struct  files_struct* fs,用来管理这个struct  files_struct;我们把对应的描述文件结构的地址写到特定的下标里。所有为什么我们在之前打印文件描述符fd时是从3开始的,是因为前面3个地址留给了三个流,当有新的文件打开时,首先是形成struct  files结构体,然后将地址写入下标3的位置。然后返回给上层用户,我们就拿到了3这个下标了。

1ecd1b2606ed46e9956a89f231c9802c.png

 当我们在使用write和read时,都需要传入fd,本质上就是去进程的PCB中找到fd所对应的文件,就可以对文件进行操作了;

结论:fd本质是内核中进程和文件相关联的数组下标

四、一切皆文件

       对于我们的外设(IO设备),在驱动层一定对应了相应的驱动程序,包括他们各自的读写方法;他们的读写方法是不一样的;

       在操作系统层面上,对于底层的键盘、显示器、磁盘等外设,需要打开时,操作系统就会给这些外设创建一个struct file的结构体进行维护,这些结构体就包含了相关外设的属性信息,再将它们用双链表管理起来。再与上层的进程结合起来既可以执行对对应的操作了。这里就是所谓的虚拟文件系统(VFS)      

我们在C++的学习中,对多态的概念有所了解,就是多个子类继承了相同的父类,每个子类的方法都是不一样的,只要父类的指针或引用调用对应的子类,就去实现对应子类的方法。在C语言中,想要实现多态,我们的方法是通过函数指针;

       在这个struct file的结构中,就包含了读写方法的函数指针,对应到了每个外设;在上层看来,所有的文件只要调用对应外设的读写方法即可,根本不关心你到底是什么文件。

       本质上,所谓的一切皆文件,就是站在struct file的层面上看待的。

1ecd1b2606ed46e9956a89f231c9802c.png


目录
相关文章
|
23天前
|
缓存 安全 Linux
Linux 五种IO模型
Linux 五种IO模型
|
30天前
|
小程序 Linux 开发者
Linux之缓冲区与C库IO函数简单模拟
通过上述编程实例,可以对Linux系统中缓冲区和C库IO函数如何提高文件读写效率有了一个基本的了解。开发者需要根据应用程序的具体需求来选择合适的IO策略。
23 0
|
1月前
|
存储 IDE Linux
Linux源码阅读笔记14-IO体系结构与访问设备
Linux源码阅读笔记14-IO体系结构与访问设备
|
2月前
|
Linux 数据处理 C语言
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(下)
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(下)
48 0
|
2月前
|
Linux 编译器 C语言
【Linux】基础IO----理解缓冲区
【Linux】基础IO----理解缓冲区
37 0
【Linux】基础IO----理解缓冲区
|
2月前
|
缓存 网络协议 算法
【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
128 1
|
3月前
|
Linux 编译器 C语言
【Linux】基础IO_4
【Linux】基础IO_4
21 3
|
3月前
|
Linux C++
c++高级篇(三) ——Linux下IO多路复用之poll模型
c++高级篇(三) ——Linux下IO多路复用之poll模型
|
3月前
|
缓存 监控 网络协议
c++高级篇(二) ——Linux下IO多路复用之select模型
c++高级篇(二) ——Linux下IO多路复用之select模型
|
2月前
|
Linux C语言 C++
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(上)
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(上)
37 0