基础IO(上)

本章目标

1.c语言文件操作复习&&文件基本结论

2Linux文件操作

3,语言级文件操作vs系统级文件操作

1.c语言文件操作复习&&文件基本结论

1.对于一个文件来说 ,再前面我们提到过它一共可以分为两个部分,文件属性和文件内容

对于一个新建的文件来说,它也是再磁盘上占用了空间的.对于一个文件名,文件大小,文件的创建时间,这些文件的属性信息它也是数据,它也要被保存在磁盘当中.而这些文件属性信息我们管它叫做元数据,而对于一个完整的文件来说
它的数据=文件的属性信息+文件的内容
2.如果我们要访问一个文件 ,我们首先要打开这个文件,如果要打开一个文件首先要找到这个文件.

对于打开一个文件,在之前我们我们用过fopen

而要找到一个文件在我们最开始就已经说过了,一个文件唯一标识是路径+文件名.

对于文件名打开的用户是一定知道的.

而路径,可以我们指定绝对路径.

而不带路径,我们就要明确一个问题,是谁来打开我们的文件.一定是正在运行的程序来打开

那只能是进程来去打开我们的文件,我们的文件如果不指定文件路径,就会用进程自身的cwd这个环境变量进行拼接查找.而进程是在动态维护着自身的工作路径.我们可以通过像chdir这样的系统调用来进行修改进程自身的工作路径.
3.打开文件实际上是在做什么?

对于磁盘这种硬件来说,它是永久性的存储介质,因此文件可以在磁盘当中永久保存,但应为速度代差的问题.根据冯诺依曼体系规定,想要访问一个文件就一定要将文件从磁盘这种外设拷贝到内存当中 .所以对于访问一个文件来说,我们访问它无非就是访问内容或者属性.而我们需要哪一部分的数据我们就将它拷贝到内存当中.对文件的操作一共就可以分为两类.第一种是是对文件的内容进行操作,另一种就是对文件的属性操作

所以对于一个文件进行操作,本质上是进程通过cpu对文件去访问到内存当中的文件.
4.Linux的一个核心的思想就是一切皆是文件.

而这些文件一共可以分为两个部分

1.存放在磁盘当中的文件.

2.另一种就是打开的文件

对于存在磁盘当中的文件这个操作是文件系统进行管理的,我们下一个专题会说

而对于我们今天研究的是进程与文件之前的关系,本质上还是属于内存管理的范畴.

研究的是打开了的文件.

而对于这些已经打开的文件,操作系统必然要对其进行管理.如果要进行管理一定在Linux存在一个结构体去描述这些被打开文件的信息.
5.回顾c语言的文件操作接口

对于文件操作无非就三种

打开文件,读文件,和写文件

对于打开文件,我们曾经用过fopen这个接口.我们需要传两个参数文件名和打开的模式

一共有6种,对于r是只读,w只写 a是追加

这三个r+是读写都有但是是w不同点是r+不会清空文件,

w+是在写的基础增加了r的功能其他一致.

对于文件清空,我不难联想到我们之前的输入重定向

我们可以趁着这个机会模拟一下,输入重定向,输出重定向和追加重定向

输入重定向

c 复制代码
#include<stdio.h>
#include<stdlib.h>
int main(int argc,char* argv[])
{
        if(argc!=2)
                return 1;
        const char* name = argv[1];
        FILE* fp = fopen(name,"w");
        if(!fp)
        {
                perror("open error\n");
                return 1;
        }
        char* tmp = "aaaaaaa";
        fprintf(fp,"%s\n",tmp);
        fclose(fp);
        return 0;
}

对于每一次写入都会重新清空文件,如果我们在写入文件的时候不给东西那就是清空文件

c 复制代码
#include<stdio.h>
int main(int argc,char* argv[])
{
        if(argc!=2)
        {
                printf("argv is shortege\n");
                return 1;
        }
        const char* name = argv[1];
        FILE* fp = fopen(name,"r");
        if(!fp)
        {
                perror("open error\n");
                return 0;

        }

        while(1)
        {
                char buffer[128]={0};
                if(!fgets(buffer,sizeof buffer,fp))
                        break;
                printf("buffer is %s\n",buffer);

        }
        return 0;
}
c 复制代码
#include<stdio.h>
#include<stdlib.h>
int main(int argc,char* argv[])
{
        if(argc!=2)
                return 1;
        const char* name = argv[1];
        FILE* fp = fopen(name,"a");
        if(!fp)
        {
                perror("open error\n");
                return 1;
        }
        char* tmp = "aaaaaaa";
        fprintf(fp,"%s\n",tmp);
        fclose(fp);
        return 0;
}

我们可以将写的函数从w改成a就可以,

对于上面的操作我们先简单了解下即可,后面我们会详细介绍的.
6.我们也了解过像fseek,ftell这种文件操作,这种涉及的是文件读写位置的操作.

我们可以把文件理解成一个以为数组,无论是中间文件开始读写还是从中间开始读写,他们都是从这个文件的某一个位置开始进行的读写.

如果我们把文件理解成一个一位数组,所谓找的读写位置就是这个一维数组的下标,他们本质上仍然是一个整数

**7.我们管文件打开fopen返回的一个FILE指针叫做文件句柄,**它本质上也是一个结构体,为了方便我们语言级别的操作,libc的标准库对我们的打开文件进行了封装.给我提供了这么一个结构体,为了使封装系统调用提供跨平台性,这点在c++中也是一样的,在c++我们提供了文件流ifstream ofstream这种东西,他们本质上也是类使对系统提供的文件属性进行了封装

2Linux文件操作

对于上述操作起始都是对底层系统调用做的封装,进行二次开发的库函数.在这里我们介绍,一下Linux的系统调用.

我们系统中给我们提供了这两个系统调用,实际上他们的入口函数是一个,第二个系统调用只比第一个多了一个参数,我们介绍一下第一个.

第一个参数与fopen一致,第二个参数是这个文件以什么模式打开的.对于已经存在的文件两个参数的open足够了,mode这个参数主要是给我们提供未创建的文件设置权限的.

它的这个权限也受umask影响.

对于flag参数,在这里linux1采用的是用位图的方式进行标致的.

这一点与qt和c++的文件操作是一致的.我们可以通过|的方式增加标识位通过&的方式去检查标志位.

我们依此介绍一下常见的标志位

O_RDONLY和O_WRONLY只读和只写的标志位

创建文件的标志位

追加文件的标志位

清空文件的标志位

在我们语言级的文件操作对于只读操作我们实际上就只用了O_RDONLY这样一个标志位

写操作实际上是O_WDONLY和O_TRUNC 以及O_CREAT

这三个标志位的组合.

对于已经存在的文件我们要清空,不存在的文件我们要进行创建

而对于追加仅仅是将清空的标志位换成O_APPEND 这个标志位.

而对于这两个系统调用他们成功的时候会返回一个fd的东西

我们管它叫做文件描述符.我们可以实验一下,我们返回一个文件打开的时候会返回几

c 复制代码
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
	int fd = open("log.txt",O_RDONLY);
	if(fd<0)
	{
		perror("open fail\n");
		return 1;
	}
	printf("file fd is %d \n" ,fd);

	return 0;
}

我们在指令能够看到我们新打开的文件描述符是3,那么这里就有一个文件,0,1,2是谁?

实际上当我们进程启动的时候,我们的进程的时候,会默认打开三个文件流,标准输入,标准输出,标准错误.

这三个文件流默认封装了0,1,2三个文件描述符在FILE文件句柄当中,所以当我们打开一个文件描述符会给它按递增的规律分配3,在这里只是简单介绍下它的分配规则,后面我们会细说的.

3,语言级文件操作vs系统级文件操作

那么什么是文件描述符?

如果我们想要打开一个文件,就需要将它从磁盘当中拷贝到内存当中,这一步是由硬件提供给os的驱动程序当中的读写操作来实现的,不需要我们管.

当这个文件已经加载到了内存当中.我们就需要将它管理起来,给他创建一个描述它结构信息的结构体.这个结构体在Linux当中是file类型的,他们是被os管理起来的.

而一个进程它需要找到它打开的文件就需要拿到这个结构体的地址.

而这个结构体的地址就需要被保存到pcb当中,而对于一个进程,它可能打开多个文件.

它就需要将这些已经打开的文件的地址进行管理,而在task_struct当中,有一个file_struct类型的指针

在这个结构体当中有一个fd_array的数组,file*类型的数组,这里面保存的就是已经打开的文件的地址,而文件描述符就是这个数组的下标.

而对于linux来说os是通过进程来进行文件操作的,它也就是只认识fd.

既然0是标准输入的文件描述符,我们想向一个显示器文件写入信息就可以直接使用

write系统调用向显示器写内容

向从键盘读文件就可以通过read系统调用从键盘读文件.

我们现在演示一下,

c 复制代码
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
        int fd = open("log.txt",O_RDONLY);
        if(fd<0)
        {
                perror("open error\n");
                return 1;
        }
        char buffer[128]={0};
        read(fd,buffer,sizeof buffer);
        printf("%s",buffer);

        return 0;
}
c 复制代码
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
        umask(0);
        int fd = open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
        if(fd<0)
        {
                perror("open fail\n");
                return 1;
        }
        const char* tmp = "bbbbbbb\n";
        write(fd,tmp,sizeof tmp);


        return 0;
}
相关推荐
DeeplyMind1 小时前
第7章:DRM内核调试技术:7.1 DRM DebugFS的使用
linux·驱动开发·drm·debugfs·drm debugfs
提伯斯6461 小时前
Fast-LIO到MAVROS视觉定位转换
linux·ros·无人机·mid360·fasltlio
天码-行空1 小时前
Oracle 19c(19.3.0.0)完整安装教程(Windows+Linux双环境)
linux·运维·数据库·oracle
e***98572 小时前
C++跨平台开发的5大核心挑战与突破
开发语言·c++
哈哈哈hhhhhh2 小时前
使用 Node.js 从零开始构建你自己的 Web 服务器
服务器·node.js
搞全栈小苏2 小时前
使用 nvm(不破坏系统)Linux 上把 Node.js / npm 升级到你指定版本(Node v23.x、npm 10.x)
linux·npm·node.js
ONLYOFFICE2 小时前
ONLYOFFICE 桌面编辑器现已推出 Linux ARM 版本
linux·运维·arm开发
Ydwlcloud2 小时前
面向全球用户的网站,AWS是唯一选择吗?
大数据·服务器·人工智能·云计算·aws
橘颂TA2 小时前
【剑斩OFFER】算法的暴力美学——leetCode 662 题:二叉树最大宽度
c++·算法·结构与算法