天天看点

[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口

文件描述符:

  1. 文件 = 文件内容 + 文件属性。 (文件属性也是数据-->即便你创建一个空文件,也要占据磁盘空间)
  2. 文件操作 = 文件内容的操作 + 文件属性的操作。(有可能在操作的过程中,即改变内容,有改变属性)
  3. 所谓的"打开"文件,究竟在干什么?将文件的属性或内容加载到内存汇总! -- 冯诺依曼体系决定
  4. 是不是所有的文件,都会处于被打开的状态?绝对不是!没有被打开的文件,在哪儿里?只在磁盘上存储着!
  5. 打开的文件(内存文件)和磁盘文件
  6. 通常我们打开文件、访问文件、关闭文件。是谁在进行相关操作?fopen,fclose,fread,fwrite....... ---》 代码 --》程序 --》 当我们的文件程序运行起来的时候,才会执行对应的代码,然后才是真正的对文件进行相关的操作。
  7. 进程和打开文件的关系

0.复习文件操作(C语言)

0.1由一段C语言文件操作产出几个问题

#include <stdio.h>
int main()
{
    FILE *fp = fopen("log.txt","w");//写入
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    const char* msg = "hello file";
    int cnt = 1;
    while(cnt < 20)
    {
        fprintf(fp,"%s:%d\n",msg,cnt++);
    }
    fclose(fp);
    return 0;
}      

运行结果:

[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口

这是我们写的一段最简单的C语言代码,这段代码可以产出几个问题:

0.1.1 log.txt生成时没有带路径,默认这个文件会在哪里形成呢?

我们都知道是在当前路径。那么什么是当前路径呢?当前路径是源代码所在的路径吗?其实这种说法是一种感性的认识,其实当前路径是进程所在的路径。为了验证这一结论。我们使用命令来查看一下。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
    FILE *fp = fopen("log.txt","w");//写入
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    printf("pid:%d\n",getpid());//获取当前进程的pid
    while(1)
    {
        sleep(1);
    }
    const char* msg = "hello file";
    int cnt = 1;
    while(cnt < 20)
    {
        fprintf(fp,"%s:%d\n",msg,cnt++);
    }
    fclose(fp);
    return 0;
}      

使用指令查看

ls /proc/pid值 -l      
[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口

我们发现有一个cwd(current working directory)--当前工作路径

因此我们验证了:当前路径是当前进程所处的工作路径。

此时我们如果对当前工作路径进行修改,我们仍然可以得到想要的结果(此时我们更改当前的工作路径到/home/Lxy)

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
    chdir("/home/Lxy");//更改当前进程的工作路径
    FILE *fp = fopen("log.txt","w");//写入
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    printf("pid:%d\n",getpid());
    while(1)
    {
        sleep(1);
    }
    const char* msg = "hello file";
    int cnt = 1;
    while(cnt < 20)
    {
        fprintf(fp,"%s:%d\n",msg,cnt++);
    }
    fclose(fp);
    return 0;
}      

我们继续使用指令来进行查看

ls /proc/pid值 -l      
[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口
[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口

0.1.2 复习a选项

[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口

a 选项是一个写入操作,写入到文件的结尾。也就是追加操作。我们赶紧有C语言来看看效果

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
    FILE *fp = fopen("log.txt","a");//写入
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    const char* msg = "hello file";
    int cnt = 1;
    while(cnt <= 5)
    {
        fprintf(fp,"%s:%d\n",msg,cnt++);
    }
    fclose(fp);
    return 0;
}      
[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口

0.1.3 复习w选项

[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口

我们看到w操作解释的第一句话是Truncate file to zero length or create text file for writing. (将文件截断为零长度或创建文本文件进行写入).意思就是如果文件不存在则创建之;如果文件已经存在则从头开始写入。我们首先验证一下存在不写会有什么结果。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
    FILE *fp = fopen("log.txt","w");//写入
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    fclose(fp);
    return 0;
}      
[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口

我们看到了文件被清空了。因此我们得到当我们以'w'方式打开文件,准备写入时,其实文件已经被清空了。这是w和a的一个最大的区别!

0.1.4 复习读操作

fgets

fgets是从特定的文件流(FILE * stream)中读取特定的数据(char *s),大小是size.返回值成功了就是读取的起始地址。我们也用C语言来实现一下

[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口
[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
    //chdir("/home/Lxy");//更改当前进程的工作路径
    FILE *fp = fopen("log.txt","r");//写入
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    char buffer[64];
    while(fgets(buffer,sizeof(buffer),fp) != NULL)
    {
        printf("echo:%s",buffer);
    }
    
    fclose(fp);
    return 0;
}      
[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口

我们根据读操作写一个小程序玩一玩,我们的想法是./myfile filename 可以打印出文件的内容

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        printf("Usage:%s filename\n",argv[0]);
        return 1;
    }
    FILE * fp = fopen(argv[1],"r");
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    char buffer[64];
    while(fgets(buffer,sizeof(buffer),fp) != NULL)
    {
        printf("%s",buffer);
    }
    fclose(fp);
    return 0;
}      

我们来一起看看吧~我们发现我们所写的和系统提供的cat指令大差不大啦~(感兴趣的小伙伴自己也尝试一下吧)

[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口

至此,我们文件基本操作(C语言)就复习到这里~

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

我们回归理论:当我们像文件写入的时候,最终是不是像磁盘写入?肯定是,那么操作系统是硬件,只有操作系统有资格像硬件写入。那么能绕开操作系统吗?答案是不能。所有的上层访问文件的操作都必须贯穿操作系统。操作系统是如何被上层使用的呢?答案肯定是使用操作系统提供的相关系统调用!

那么这里有两个问题?

  1. 如何理解printf? ---- 封装了系统调用接口
  2. 我们怎么从来没见过系统调用接口呢? ---- 所有的语言都对系统接口做了封装

那么为什么要做封装?

  1. 原生系统接口,使用成本比价高(后期我们会看并且使用系统接口)
  2. 直接使用原生系统接口,语言不具备跨平台性。因此操作系统不同,操作系统所提供的原生接口不同。那么封装是如何解决跨平台问题的呢?--- 穷举所有的底层接口+条件编译 (根据具体对象调用对应的系统调用接口) 比如你用的是Linux系统,C语言就会把win,MacOs等接口关闭,只露出Linux的接口。上层用户用的都是一个函数,但是其实底层做了封装。

1.1 见一见系统接口

1.1.1 open

open是文件系统接口最重要的接口,没有之一。因此我们先来看看这个接口。

第一个参数(const char* pathname):带路径的文件名

第二个参数(flags):打开文件传递的选项(下面会重点介绍)

第三个参数(mode):设置权限

返回值:int , -1表示出现错误 (C语言FILE*)

[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口
[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口

第一个参数介绍:

带路径的文件名,如果只写文件名默认在当前路径下创建。

第二个参数介绍:

flags标志位有很多选项,其中这些选项都是宏。其中系统传递标记位时,是用位图结构来进行传递的。因此每一个宏标记只需要有一个比特位是1,并且有其他宏对应的值不能重叠。在这里我们讲自己写一个接口来验证一下

[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口
[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口
#include <stdio.h>

#define PRINT_A 0x1 // 0000 00001
#define PRINT_B 0x2 // 0000 00010
#define PRINT_C 0x4 // 0000 00100
#define PRINT_D 0x8 // 0000 01000
#define PRINT_DFL 0x0

void Show(int flags)
{
    if(flags & PRINT_A) printf("Hello A\n");
    if(flags & PRINT_B) printf("Hello B\n");
    if(flags & PRINT_C) printf("Hello C\n");
    if(flags & PRINT_D) printf("Hello D\n");

    if(flags == PRINT_DFL) printf("hello Default\n");
}

int main()
{
    printf("hello Default\n");
    Show(PRINT_DFL);

    printf("Hello A\n");
    Show(PRINT_A);
    printf("Hello B\n");
    Show(PRINT_B);
    printf("PRINT_A和PRINT_B\n");
    Show(PRINT_A | PRINT_B);
    printf("PRINT_C和PRINT_D\n");
    Show(PRINT_C | PRINT_D);
    printf("PRINT_A和PRINT_B和PRINT_C和PRINT_D\n");
    Show(PRINT_A | PRINT_B | PRINT_C | PRINT_D);


    return 0;
}      
[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口

通过这个例子我们就更好的理解了open的第二个参数,介绍这两个参数后,我们就可以使用open系统函数了,我们也用C语言来实现一下

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{

    int fd = open("log.txt",O_WRONLY | O_CREAT);
    if(fd < 0 )
    {
        perror("open");
        return 1;
    } 
    printf("fd: %d\n",fd);
    return 0;
}      
[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口

第三个参数:

通过打印值我们发现fd返回值确实是一个int型。而且我们发现log.txt文件的权限部分是一对乱码,原因是,当我们新建一个文件的时候,我们要设置文件的权限。因此我们得出一个结论,如果我们要创建一个不存在的文件,不能使用两个参数的open接口,而是要使用带有权限参数的open接口。我们重新创建一下log.txt,并设置权限位0666

int main()
{

    int fd = open("log.txt",O_WRONLY | O_CREAT,0666);
    if(fd < 0 )
    {
        perror("open");
        return 1;
    } 
    printf("fd: %d\n",fd);
    return 0;

}      
[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口

此时我们发现权限比刚才的正常多了,确实不在是乱码现象了。但是0664,而不是0666。这是因为权限掩码是umask。默认的umask是0002.所以如果我们就想要权限是0666.我们手动设置umask为0即可。我们再来看看

[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口

1.1.2 close

我们把文件打开了,总得关闭文件吧。因此我们来看看关闭文件的系统接口close.因此这个接口非常简单好用。

[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口
[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口

1.1.3 write

我们把文件打开和关闭了解了之后,接下来我们要对文件进行操作了。第一个我们要了解的是write。像文件内写多西。

[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口
[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口

第一个参数(int fd):

fd,特定的文件描述符。也就是向那个文件写。

第二个参数(const void* buf):

写入缓冲区的起始地址

第三个参数(count):

写入缓冲区的大小

我们也用C语言来练一练

#include <stdio.h>
#include <string.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,0666);
    if(fd < 0 )
    {
        perror("open");
        return 1;
    } 
    printf("fd: %d\n",fd);

    //对文件操作
    int cnt = 0;
    const char *str = "hello file\n";
    while(cnt<5)
    {
        write(fd,str,strlen(str));
        cnt++;
    }
    //关闭文件
    close(fd);
    return 0;
}      
[ Linux ] 复习C文件IO相关操作及认识文件相关系统调用接口

我们看到成功的向log.txt文件写入了5条hello file

至此我们已经见过了最基本的系统接口,后续我们还要对系统调用接口详细介绍使用

(本篇完)