linux文件io

1.文件描述符

        1.1虚拟地址空间

                  虚拟地址空间的大小是由操作系统决定的,32位的系统虚拟地址大小就是2的32次方。就是4G,64位的系统就是2的64次方。   

当我们运行磁盘上一个可执行程序, 就会得到一个进程,内核会给每一个运行的进程创建一块属于自己的虚拟地址空间,并将应用程序数据装载到虚拟地址空间对应的地址上。

进程在运行过程中,程序内部所有的指令都是通过CPU处理完成的,CPU只进行数据运算并不具备数据存储的能力,其处理的数据都加载自物理内存,那么进程中的数据是如何进出入到物理内存中的呢?其实是通过CPU中的内存管理单元MMU(Memory Management Unit)从进程的虚拟地址空间中映射过去的。


          1.1.1虚拟地址为什么会存在

                如果程序直接访问物理内存,会导致如下问题:

                        1.每个进程地址不隔离,有安全的风险

                        2.内存效率低

                        3.进程中的数据地址不确定,每次都会发生变化

有了虚拟地址空间之后就可以完美的解决上边提到的所有问题了,虚拟地址空间就是一个中间层,相当于在程序和物理内存之间设置了一个屏障,将二者隔离开来。程序中访问的内存地址不再是实际的物理内存地址,而是一个虚拟地址,然后由操作系统将这个虚拟地址映射到适当的物理内存地址上。这样,只要操作系统处理好虚拟地址到物理内存地址的映射,就可以保证不同的程序最终访问的内存地址位于不同的区域,彼此没有重叠,就可以达到内存地址空间隔离的效果。

1.1.2 分区

        虚拟地址分为内核区和用户区

保留区: 位于虚拟地址空间的最底部,未赋予物理地址。任何对它的引用都是非法的,程序中的空指针(NULL)指向的就是这块内存地址。
.text段: 代码段也称正文段或文本段,通常用于存放程序的执行代码(即CPU执行的机器指令),代码段一般情况下是只读的,这是对执行代码的一种保护机制。
.data段: 数据段通常用于存放程序中已初始化且初值不为0的全局变量和静态变量。数据段属于静态内存分配(静态存储区),可读可写。
.bss段: 未初始化以及初始为0的全局变量和静态变量,操作系统会将这些未初始化变量初始化为0
堆(heap):用于存放进程运行时动态分配的内存。
堆中内容是匿名的,不能按名字直接访问,只能通过指针间接访问。
堆向高地址扩展(即“向上生长”),是不连续的内存区域。这是由于系统用链表来存储空闲内存地址,自然不连续,而链表从低地址向高地址遍历。
内存映射区(mmap):作为内存映射区加载磁盘文件,或者加载程序运作过程中需要调用的动态库。
栈(stack): 存储函数内部声明的非静态局部变量,函数参数,函数返回地址等信息,栈内存由编译器自动分配释放。栈和堆相反地址“向下生长”,分配的内存是连续的。
命令行参数:存储进程执行的时候传递给main()函数的参数,argc,argv[]
环境变量: 存储和进程相关的环境变量, 比如: 工作路径, 进程所有者等信息

2.文件描述符

        2.1文件描述符

在Linux系统中一切皆文件,系统中一切都被抽象成了文件。对这些文件的读写都需要通过文件描述符来完成。标准C库的文件IO函数使用的文件指针FILE*在Linux中也需要通过文件描述符的辅助才能完成读写操作。FILE其实是一个结构体,其内部有一个成员就是文件描述符

        2.2文件描述符表

前面讲到启动一个进程就会得到一个对应的虚拟地址空间,这个虚拟地址空间分为两大部分,在内核区有专门用于进程管理的模块。Linux的进程控制块PCB(process control block)本质是一个叫做task_struct的结构体,里边包括管理进程所需的各种信息,其中有一个结构体叫做file ,我们将它叫做文件描述符表,里边有一个整形索引表,用于存储文件描述符。

内核为每一个进程维护了一个文件描述符表,索引表中的值都是从0开始的,所以在不同的进程中你会看到相同的文件描述符,但是它们指向的不一定是同一个磁盘文件。

Linux中用户操作的每个终端都被视作一个设备文件, 当前操作的终端文件可以使用 /dev/tty表示。

打开的最大文件数

每一个进程对应的文件描述符表能够存储的打开的文件数是有限制的, 默认为1024个,这个默认值是可以修改的,支持打开的最大文件数据取决于操作系统的硬件配置。

默认分配的文件描述符

当一个进程被启动之后,内核PCB的文件描述符表中就已经分配了三个文件描述符,这三个文件描述符对应的都是当前启动这个进程的终端文件(Linux中一切皆文件,终端就是一个设备文件,在 /dev 目录中)

STDIN_FILENO:标准输入,可以通过这个文件描述符将数据输入到终端文件中,宏值为0。
STDOUT_FILENO:标准输出,可以通过这个文件描述符将数据通过终端输出出来,宏值为1。
STDERR_FILENO:标准错误,可以通过这个文件描述符将错误信息通过终端输出出来,宏值为2
这三个默认分配的文件描述符是可以通过close()函数关闭掉,但是关闭之后当前进程也就不能和当前终端进行输入或者输出的信息交互了。

给新打开的文件分配文件描述符

因为进程启动之后,文件描述符表中的0,1,2就被分配出去了,因此从3开始分配
在进程中每打开一个文件,就会给这个文件分配一个新的文件描述符,比如:
通过open()函数打开 /hello.txt,文件描述符 3 被分配给了这个文件,保持这个打开状态,再次通过open()函数打开 /hello.txt,文件描述符 4 被分配给了这个文件,也就是说一个进程中不同的文件描述符打开的磁盘文件可能是同一个。
通过open()函数打开 /hello.txt,文件描述符 3 被分配给了这个文件,将打开的文件关闭,此时文件描述符3就被释放了。再次通过open()函数打开 /hello.txt,文件描述符 3 被分配给了这个文件,也就是说打开的新文件会关联文件描述符表中最小的没有被占用的文件描述符。

总结:

每个进程对应的文件描述符表默认支持打开的最大文件数为 1024,可以修改
每个进程的文件描述符表中都已经默认分配了三个文件描述符,对应的都是当前终端文件(/dev/tty)
每打开新的文件,内核会从进程的文件描述符表中找到一个空闲的没有别占用的文件描述符与其进行关联
文件描述符表中不同的文件描述符可以对应同一个磁盘文件
每个进程文件描述符表中的文件描述符值是唯一的,不会重复

3.文件描述符的创建和使用

每个系统都有自己的专属函数,我们习惯叫做系统函数,系统函数是用户和系统内核的桥梁

        3.1 open/close

              3.1.1 函数原型  

                1.open的函数原型如下:       

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

/*
open是一个系统函数, 只能在linux系统中使用, windows不支持
fopen 是标准c库函数, 一般都可以跨平台使用, 可以这样理解:
        - 在linux中 fopen底层封装了Linux的系统API open
        - 在window中, fopen底层封装的是 window 的 api
*/
// 打开一个已经存在的磁盘文件
int open(const char *pathname, int flags);
// 打开磁盘文件, 如果文件不存在, 就会自动创建
int open(const char *pathname, int flags, mode_t mode);

参数介绍:

pathname: 被打开的文件的文件名

flags: 使用什么方式打开指定的文件,这个参数对应一些宏值,需要根据实际需求指定

必须要指定的属性, 以下三个属性不能同时使用, 只能任选其一
O_RDONLY: 以只读方式打开文件
O_WRONLY: 以只写方式打开文件
O_RDWR: 以读写方式打开文件
可选属性, 和上边的属性一起使用
O_APPEND: 新数据追加到文件尾部, 不会覆盖文件的原来内容
O_CREAT: 如果文件不存在, 创建该文件, 如果文件存在什么也不做
O_EXCL: 检测文件是否存在, 必须要和 O_CREAT 一起使用, 不能单独使用: O_CREAT | O_EXCL
检测到文件不存在, 创建新文件
检测到文件已经存在, 创建失败, 函数直接返回-1(如果不添加这个属性,不会返回-1)
mode: 在创建新文件的时候才需要指定这个参数的值,用于指定新文件的权限,这是一个八进制的整数

这个参数的最大值为:0777

创建的新文件对应的最终实际权限, 计算公式: (mode & ~umask)

umask 掩码可以通过 umask 命令查看
$ umask
0002
假设 mode 参数的值为 0777, 通过计算得到的文件权限为 0775
# umask(文件掩码):  002(八进制)  = 000000010 (二进制)  
# ~umask(掩码取反): ~000000010 (二进制) = 111111101 (二进制)  
# 参数mode指定的权限为: 0777(八进制) = 111111111(二进制)
# 计算公式: mode & ~umask
             111111111
       &     111111101
            ------------------
             111111101    二进制
            ------------------
             mod=0775     八进制  
返回值:

成功: 返回内核分配的文件描述符, 这个值被记录在内核的文件描述符表中,这是一个大于0的整数
失败: -1。

2.close函数原型

#include <unistd.h>
int close(int fd);

函数参数: fd 是文件描述符, 是open() 函数的返回值
函数返回值: 函数调用成功返回值 0, 调用失败返回 -1

3.打开已存在的文件

我们可以使用open()函数打开一个本地已经存在的文件, 假设我们想要读写这个文件, 操作代码如下:


// open.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

int main()
{
    // 打开文件
    int fd = open("abc.txt", O_RDWR);
    if(fd == -1)
    {
        printf

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值