【ONE·Linux || 基础IO(一)】

news2024/9/17 8:49:14

总言

  文件输入与输出相关介绍:语言层面/系统层面文件调用接口举例、文件描述符、重定向说明、缓冲区理解。

文章目录

  • 总言
  • 1、文件输入与输出
    • 1.1、预备知识
    • 1.2、语言层面:回归C语言中文件相关接口
      • 1.2.1、打开文件和关闭文件:对当前路径的理解
      • 1.2.2、文件读写操作
        • 1.2.2.1、解释字符串结尾标识符\0,\n
        • 1.2.2.2、fopen以 "w" 方式打开文件:引出输出重定向>
        • 1.2.2.3、fopen以"a"方式打开文件:引出追加重定向>>
        • 1.2.2.4、fopen以"r"方式打开:演示fgets函数,写一个cat命令
    • 1.3、系统层面:直接使用系统的文件接口
      • 1.3.1、打开文件和关闭文件:
        • 1.3.1.1、open:文件打开
      • 1.3.1.2、close:文件关闭
      • 1.3.2、文件读写操作
        • 1.3.2.1、write:如何达到语言层"w"的效果
        • 1.3.2.2、write:如何达到语言层"r"的效果
        • 1.3.2.3、read:文件读取
    • 1.4、分析系统接口的细节:引入fd文件描述符,解释一些周边问题
      • 1.4.1、fd文件描述符返回值
      • 1.4.2、对fd的理解
      • 1.4.3、fd分配规则和重定向原理
        • 1.4.3.1、原理演示
        • 1.4.3.2、常规写法
        • 1.4.3.3、重新理解Linux下一切皆文件
      • 1.4.4、解释缓冲区
        • 1.4.4.1、问题一:什么是缓冲区?谁提供的?
        • 1.4.4.2、问题二:为什么要缓冲区?
        • 1.4.4.3、问题三:缓冲区在哪?

  
  

1、文件输入与输出

1.1、预备知识

  1)、文件操作的范畴
  说明:根据之前所学,文件=文件内容+属性(二者都是数据)。因此,用户对文件的操作无外乎①对文件内容的操作、②对文件属性的操作。
  
  
  

  2)、文件存放在磁盘(硬件)上,那么谁在访问文件?
  说明:访问文件,实际上是编写出的可执行程序加载到内存中,形成进程运行起来,在进程内部执行相关代码,调用到库中相关的函数,从而调用操作系统提供的接口,访问到文件。
在这里插入图片描述
  文件类系统调用接口:要向硬件中写入,只有操作系统拥有该权限。作为用户层,若我们需要涉及硬件读写相关,那么操作系统在保护其结构的基础上,需要为我们提供相应的接口。
  
  
  
  3)、文件类系统调用接口存在意义
  理由一:统一性。为了便捷使用文件类系统调用接口,在上语言层面,对这些接口进行了各自的封装,导致不同的语言间,它们各自的文件访问接口不同(比如各类参数传递、函数名等等)。若单独学习语言级别的文件调用接口,相对繁多且杂,但它们的共性是底层都使用了系统调用接口,在一个平台,OS只有一个,那么这样的接口也就只有一套,便于我们了解学习各类文件输入输出。
  
  理由二:跨平台。实际上,语言级别的系统接口是必要的,若不提供它,所有访问文件的操作,都需要直接使用OS的接口,这样一来所编写的文件代码就无法在在其它OS平台上运行,即不具备跨平台性。
  
  
  
  4)、什么是文件
  站在系统角度,能够被input读取,或者output写出的设备,就叫做文件。狭义的文件指普通磁盘文件,广义的文件有:显示器、键盘、网卡、声卡、显卡、磁盘等等几乎所有外设。
  
  
  
  

1.2、语言层面:回归C语言中文件相关接口

1.2.1、打开文件和关闭文件:对当前路径的理解

  1)、问题引入

  在C语言中,fopen可以打开一个文件,相关函数内容可通过man fopen查阅。在学习C语言时,我们也曾了解过该函数,在一些模式下,若我们打开的文件不存在,则会创建新的文件,因此有了以下问题。

在这里插入图片描述
  
  问题:①打开文件时,若文件不存在,则创建文件,是谁执行了创建文件的操作?②创建出的文件默认路径在哪?③为什么是这个默认路径?
  
  
  
  2)、说明
  演示所用代码:

#include<stdio.h>

int main()
{
    //打开文件
    FILE* fd=fopen("log1.txt","w");
    if(fd==NULL)
    {
        perror("fopen");
        return -1;//文件读取失败
    }

    //文件操作
    
    //关闭文件
    fclose(fd);

    return 0;
}

  
  
  1、如下图,当文件不存在时,操作系统会在默认的当前路径下创建文件,那么,①什么叫做当前路径?②当前路径是否等同于源代码路径?
在这里插入图片描述
  
  

  回答②:文件创建的当前路径和源代码路径并不能完全等同,以下为相关验证:所谓的当前路径,通常情况下与可执行程序所在的路径等同,但有些平台也不一致,如window下,VS会将其分文件夹存放。
在这里插入图片描述

  
  回答①:实际上,进程运行起来,每个进程都会记录自己的exe程序所在路径和cwd当前工作路径,所创建的文件路径正是系统根据cwd路径与fopen中参数path指定的文件名做拼接而成。
在这里插入图片描述
  若将进程的工作目录cwd改变,则对应创建的文件所在路径也会改变。
  
  
  
  
  
  

1.2.2、文件读写操作

       #include <stdio.h>
       size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
       size_t fwrite(const void *ptr, size_t size, size_t nmemb,
                     FILE *stream);
           
     
       #include <stdio.h>
       int printf(const char *format, ...);
       int fprintf(FILE *stream, const char *format, ...);

  
  
  

1.2.2.1、解释字符串结尾标识符\0,\n

  以下为基本演示,我们使用一些文件函数向文件中写入:
在这里插入图片描述

  问题:上述fwrite(str1,strlen(str1),1,fp);中,考虑到字符串结尾为\0,strlen计算时是否需要+1,即fwrite(str1,strlen(str1)+1,1,fp)
在这里插入图片描述
  回答:不需要。如上图,计入\0反而读入文件后会在多出一个^@。字符串以\0结尾是C语言的规定(语言层面的规则),文件调用为操作系统执行,系统层面并不需要遵守语言层面的规则,文件中只需要保存有效数据即可。
  
  
  相关演示代码:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main()
{
    //打开文件
    FILE* fp=fopen("log1.txt","w");
    if(fp==NULL)
    {
        perror("fopen");
        return -1;//文件读取失败
    }

    //文件操作
    const char* str1="No matter what they tell us\n";
    fwrite(str1,strlen(str1),1,fp);
    //fwrite(str1,strlen(str1)+1,1,fp);//error

    const char* str2="cologne and white sunshine\n";
    fprintf(fp,"%s",str2);

    const char* str3="Across the stars ,across the moon\n";
    fputs(str3,fp);

    //关闭文件
    fclose(fp);
    
    return 0;
}

  
  
  

1.2.2.2、fopen以 “w” 方式打开文件:引出输出重定向>

  1)、基本解释

       w      Truncate  file  to zero length or create text file for writing.  The stream
              is positioned at the beginning of the file.

  以"w"方式读写文件在上述我们已经基本演示过,此处主要来探讨其细节部分:
在这里插入图片描述
  
  2)、重定向引入
  对于输出重定向,其类似于此处的"w",会产生清空文件内容并重头开始写入的效应。

[wj@VM-4-3-centos T0728]$ echo Everything has its time > log1.txt
[wj@VM-4-3-centos T0728]$ cat log1.txt
Everything has its time
[wj@VM-4-3-centos T0728]$ > log1.txt
[wj@VM-4-3-centos T0728]$ cat log1.txt
[wj@VM-4-3-centos T0728]$ 

在这里插入图片描述

  
  
  

1.2.2.3、fopen以"a"方式打开文件:引出追加重定向>>

  1)、基本解释

       r      Open  text  file for reading.  The stream is positioned at the beginning of
              the file.

在这里插入图片描述
  
  2)、重定向引入
  对于追加重定向,其类似于此处的"r"操作,会不断向文件中新增内容。

[wj@VM-4-3-centos T0728]$ echo Return To Innocence >>log1.txt
[wj@VM-4-3-centos T0728]$ cat log1.txt
so keep your head up
so keep your head up
so keep your head up
so keep your head up
Return To Innocence
[wj@VM-4-3-centos T0728]$ 

  
  
  上述小结相关代码如下:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main()
{
    //打开文件
    FILE* fp=fopen("log1.txt","a");
    if(fp==NULL)
    {
        perror("fopen");
        return -1;//文件读取失败
    }

    //文件操作
    const char* str4="so keep your head up\n";//演示”r“
    puts(str4,fp);

    //关闭文件
    fclose(fp);
   
    return 0;
}

  
  
  

1.2.2.4、fopen以"r"方式打开:演示fgets函数,写一个cat命令

  1)、基本解释

       #include <stdio.h>
       int fgetc(FILE *stream);
       char *fgets(char *s, int size, FILE *stream);

       fgets() reads in at most one less than size characters from stream and stores them
       into the buffer pointed to by s.  Reading stops after an EOF or a newline.   If  a
       newline  is read, it is stored into the buffer.  A terminating null byte ('\0') is
       stored after the last character in the buffer.

  相关演示如下:fgets(buffer,sizeof(buffer),fp)fp指向log1.txt,故fgets读取到文件,并将其内容逐行打印到显示器上fprintf(stdout,"%s",buffer);
在这里插入图片描述

  代码如下:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main()
{
    //打开文件
    FILE* fp=fopen("log1.txt","r");
    if(fp==NULL)
    {
        perror("fopen");
        return -1;//文件读取失败
    }

    //文件操作
    char buffer[64];
    while(fgets(buffer,sizeof(buffer),fp)!=NULL)
    {
        fprintf(stdout,"%s",buffer);
    }

    //关闭文件
    fclose(fp);

    return 0;
}

  
  2)、写一个cat命令
  基于上述操作,广义角度显示器等外设也是文件,我们可以照猫画虎写一个简易的cat指令,要求为读取命令行参数,并将其内容显示到显示器上。相关演示如下:

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

//演示cat指令
int main(int argc, char* argv[])
{
    //判断命令行参数是否满足
    if(argc!=2)
    {
        printf("argv error!\n");
        return 1;//命令行参数不满足
    }
    //打开文件
    FILE* fp=fopen(argv[1],"r");
    if(fp==NULL)
    {
        perror("fopen");
        return 2;//文件读取失败
    }

    //文件操作
    char buffer[64];
    while(fgets(buffer,sizeof(buffer),fp)!=NULL)
    {
        fprintf(stdout,"%s",buffer);
    }

    return 0;
}

  演示结果如下:
在这里插入图片描述

[wj@VM-4-3-centos T0728]$ ls
log1.txt  makefile  myfile.c  myfile.o
[wj@VM-4-3-centos T0728]$ ./myfile.o 
argv error!
[wj@VM-4-3-centos T0728]$ ./myfile.o log1.txt
so keep your head up
so keep your head up
so keep your head up
so keep your head up
Return To Innocence
[wj@VM-4-3-centos T0728]$ cat log1.txt
so keep your head up
so keep your head up
so keep your head up
so keep your head up
Return To Innocence
[wj@VM-4-3-centos T0728]$ ./myfile.o log1.txt > tmp.txt
[wj@VM-4-3-centos T0728]$ ls
log1.txt  makefile  myfile.c  myfile.o  tmp.txt
[wj@VM-4-3-centos T0728]$ ./myfile.o temp.txt
fopen: No such file or directory
[wj@VM-4-3-centos T0728]$ ./myfile.o tmp.txt
so keep your head up
so keep your head up
so keep your head up
so keep your head up
Return To Innocence
[wj@VM-4-3-centos T0728]$ 

  
  
  

1.3、系统层面:直接使用系统的文件接口

1.3.1、打开文件和关闭文件:

1.3.1.1、open:文件打开

  1)、语言库接口和系统调用接口的上下层关系
  C语言库函数接口:fopen、fclose、fread、fwrite
  系统接口:open、close、read、write
  可以认为,f#系列的函数,都是对系统调用的封装。
  
  
  2)、open文件打开相关参数介绍与演示

在这里插入图片描述man open:可查看关于open的相关介绍

NAME
       open, creat - open and possibly create a file or device

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

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

       int creat(const char *pathname, mode_t mode);

  说明:
  pathname: 要打开或创建的目标文件
  flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。参数如下:
     O_RDONLY: 只读打开
     O_WRONLY: 只写打开
     O_RDWR : 读,写打开
     PS:上述三个常量,必须指定一个且只能指定一个。
     O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
     O_APPEND: 追加写

  
  
  返回值: 若成功则返回新打开的文件描述符(file descriptor), 若失败则返回-1。

RETURN VALUE
       open() and creat() return the new file descriptor, 
       or -1 if an error occurred (in which case, errno is set appropriately).

在这里插入图片描述

  
  

在这里插入图片描述如何给函数传递标志位?(对flags的认识理解)

  如上,flags中传入参数为一个个宏定义,那么,如何使用这些宏该?它们是怎么达成对应效果的?
  实际上,我们将flags这类选项称之为函数标记位,如上述int flags,该参数需要具有传入多个参数的能力,而要达到此效果,可根据参数类型(int整形,32位下32个比特位),使用不同比特位表示不同种参数选项。以下为一个简单的演示例子:

#include<stdio.h>
#include<stdlib.h>

#define ONE 0x1 //0000 0001
#define TWO 0x2 //0000 0010
#define THREE 0x4 //0000 0100

//写一个show函数,其能够根据传入的flags参数选择,达成多重条件
void show(int flags)
{
    if(flags & ONE)
    { printf("Achieve condition ONE\n"); }

    if(flags & TWO)
    { printf("Achieve condition TWO\n");  }

    if(flags & THREE)
    { printf("Acheive condition THREE\n"); }
}

int main()
{
    show(ONE);
    printf("-----------\n");
    show(ONE | TWO);
    printf("-----------\n");
    show(ONE | TWO | THREE);
    printf("-----------\n");
    show(TWO);
    printf("-----------\n");

    return 0;
}

  演示结果如下:
在这里插入图片描述

  
  

在这里插入图片描述演示一:如何使用这两个open

  函数声明如下:

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

  
  说明一:通常,只含两个参数的open适用于文件存在的情况。代码如下:

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
    //打开文件:系统接口
    int fd=open("log01.txt",O_WRONLY);//以写的方式打开
    if(fd < 0)
    {
        perror("open");
        return 1;//文件打开失败
    }

    //文件操作
    printf("open success,fd:%d\n",fd);

    return 0;
}


  演示结果如下:

[wj@VM-4-3-centos T0729]$ ls
makefile  myfile.c
[wj@VM-4-3-centos T0729]$ make
gcc -o myfile.o myfile.c

[wj@VM-4-3-centos T0729]$ ls //可看到不存在文件log01.txt
makefile  myfile.c  myfile.o
[wj@VM-4-3-centos T0729]$ ./myfile.o //运行后显示open打开失败
open: No such file or directory

  
  

在这里插入图片描述演示二:成功获取返回值

  说明二: 实际上,我们在应用层看到的一个简单的操作,在系统层面可能会经过繁杂的操作才能得到。例如上述,在库函数层面,我们使用"w"以写的方式打开文件时,若文件不存在则会创建文件。这里我们以O_WRONLY写的方式打开,但实际上并未创建不存在文件,这就需要我们在flags文件标志位中传入多个参数。以下为相关演示:

  相比于上述只是对flags参数传入做了小改动:int fd=open("log01.txt",O_WRONLY | O_CREAT);

[wj@VM-4-3-centos T0729]$ make
gcc -o myfile.o myfile.c
[wj@VM-4-3-centos T0729]$ ls
makefile  myfile.c  myfile.o
[wj@VM-4-3-centos T0729]$ ./myfile.o 
open success,fd:3 //可以看到成功获取返回值,即文件标记符fd=3。
[wj@VM-4-3-centos T0729]$ 

  
  同时,其也引出了下述问题:文件权限。
在这里插入图片描述
  
  
  

在这里插入图片描述演示三:若文件不存在,需要使用带三个参数的open,第三个参数mode可设置文件默认权限

  以下为man open中关于mode的部分节选内容,需要注意,实际创建出来的文件权限是(mode & ~umask)共同作用的结果。

              mode specifies the permissions to use in case a new file is created.  This argument must be supplied  
              when  O_CREAT  is  specified in flags; if O_CREAT is not specified, then mode is ignored.
              The effective permissions are modified by the process's umask in the usual way: The  permissions
              of the created file are (mode & ~umask).  Note that this mode applies only to future accesses of
              the newly created file; the open() call  that  creates  a  read-only  file  may  well  return  a
              read/write file descriptor.

              The following symbolic constants are provided for mode:

              S_IRWXU  00700 user (file owner) has read, write and execute permission

              S_IRUSR  00400 user has read permission

              S_IWUSR  00200 user has write permission

              S_IXUSR  00100 user has execute permission

在这里插入图片描述

  若我们希望传递参数时,Mode输入的权限不受umask影响,即输入0666就是0666,那么可以使用系统函数umask来解决。umask(0),清除配置中0002带来的影响,将当前程序中所创建的文件权限掩码设置为0000。

NAME
       umask - set file mode creation mask

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

       mode_t umask(mode_t mask);

在这里插入图片描述
  
  
  

1.3.1.2、close:文件关闭

  man colse可查看相关函数具体介绍,我们只用根据打开文件所提供的文件描述符,即可关闭对应文件。

NAME
       close - close a file descriptor

SYNOPSIS
       #include <unistd.h>

       int close(int fd);

  
  
  
  

1.3.2、文件读写操作

1.3.2.1、write:如何达到语言层"w"的效果

  1)、write声明介绍和使用
  man 2 write

NAME
       write - write to a file descriptor

SYNOPSIS
       #include <unistd.h>

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

DESCRIPTION
       write() writes up to count bytes from the buffer pointed buf to the file referred to by the file descriptor fd.

  演示代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
    //打开文件:系统接口
    umask(0);
    int fd = open("log01.txt",O_WRONLY | O_CREAT,0664);
    if(fd < 0)
    {
        perror("open");
        return 1;//文件打开失败
    }

    //文件操作
    printf("open success,fd:%d\n",fd);
    const char* str1="Climb upon your star\n";
    write(fd,str1,strlen(str1));
	//文件关闭
    close(fd);
    return 0;
}

  演示结果如下:
在这里插入图片描述
  
  
  
  2)、“w”:清空文件内容,每次都从首行写入
  问题说明:以下也验证了语言层面看似简单的操作,实则系统层面有时会做多项处理。
在这里插入图片描述
  要达到上层语言库里的fopen(“XXX”,“w”)的调用效果,则在open文件时,对flags参数继续添加选项:O_WRONLY|O_CREAT|O_TRUNC

       O_TRUNC
              If the file already exists and is a regular file and the open  mode  allows
              writing (i.e., is O_RDWR or O_WRONLY) it will be truncated to length 0.  If
              the file is a FIFO or terminal device file, the O_TRUNC  flag  is  ignored.
              Otherwise the effect of O_TRUNC is unspecified.

  相关演示如下:
在这里插入图片描述
  
  相关代码如下:

int main()
{
    //打开文件:系统接口
    umask(0);
    int fd = open("log01.txt",O_WRONLY | O_CREAT | O_TRUNC ,0664);
    if(fd < 0)
    {
        perror("open");
        return 1;//文件打开失败
    }

    //文件操作
    printf("open success,fd:%d\n",fd);
    //const char* str1="Climb upon your star\n";
    const char* str1="The World";
    write(fd,str1,strlen(str1));
	//文件关闭
    close(fd);
    return 0;
}

  
  
  

1.3.2.2、write:如何达到语言层"r"的效果

       O_APPEND
              The file is opened in append mode.  Before each write(2), the  file  offset
              is  positioned  at  the end of the file, as if with lseek(2).  O_APPEND may
              lead to corrupted files on NFS  file  systems  if  more  than  one  process
              appends  data  to  a  file  at  once.  This is because NFS does not support
              appending to a file, so the client kernel has to simulate it,  which  can't
              be done without a race condition.

  相关演示如下:
在这里插入图片描述

  代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
    //打开文件:系统接口
    umask(0);
    int fd = open("log01.txt",O_WRONLY | O_CREAT | O_APPEND ,0664);
    if(fd < 0)
    {
        perror("open");
        return 1;//文件打开失败
    }

    //文件操作
    printf("open success,fd:%d\n",fd);
    //const char* str1=" A New Day Has Come\n";
    const char* str1="I know I would make it through\n";
    write(fd,str1,strlen(str1));
	//文件关闭
    close(fd);
    return 0;
}

  
  

1.3.2.3、read:文件读取

NAME
       read - read from a file descriptor

SYNOPSIS
       #include <unistd.h>

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

DESCRIPTION
       read()  attempts to read up to count bytes from file descriptor fd into the buffer
       starting at buf.

  
  演示结果如下:
在这里插入图片描述

  演示代码如下:

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

//文件读操作演示
int main()
{
    //打开文件:系统接口
    umask(0);
    //int fd = open("log01.txt",O_WRONLY | O_CREAT | O_APPEND ,0664);
    //以只读的方式打开,就不需要三参数的open(正常情况下文件本身存在,否则读了没意义)
    int fd = open("log01.txt",O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        return 1;//文件打开失败
    }

    //文件操作
    char buffer[128];//用于存储从文件中读取到的字符
    memset(buffer,'\0',sizeof(buffer));
    read(fd,buffer,sizeof(buffer));
    printf("The result:%s",buffer);
	//文件关闭
    close(fd);
    return 0;
}

  
  
  
  
  
  
  

1.4、分析系统接口的细节:引入fd文件描述符,解释一些周边问题

1.4.1、fd文件描述符返回值

  1)、问题引入与阶段理解一
  问题引入: 连续创建多个文件,观察其返回值文件描述符(file descriptor),发现fd是从3开始。为什么会存在这样的现象?

在这里插入图片描述

  相关代码:

int main()
{
    //打开文件:系统接口
    umask(0);
    int fd1 = open("log01.txt",O_WRONLY | O_CREAT | O_TRUNC ,0664);
    int fd2 = open("log02.txt",O_WRONLY | O_CREAT | O_TRUNC ,0664);
    int fd3 = open("log03.txt",O_WRONLY | O_CREAT | O_TRUNC ,0664);
    int fd4 = open("log04.txt",O_WRONLY | O_CREAT | O_TRUNC ,0664);

    //文件操作
    printf("open success,fd:%d\n",fd1);
    printf("open success,fd:%d\n",fd2);
    printf("open success,fd:%d\n",fd3);
    printf("open success,fd:%d\n",fd4);

    close(fd1);
    close(fd2);
    close(fd3);
    close(fd4);
    return 0;
}

  
  
  
  2)、验证0、1、2对应stdin、stdout、stderr
  说明: Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0(stdin), 标准输出1(stdout), 标准错误2(stderr),对应的物理设备一般是:键盘,显示器,显示器。
  
  验证代码如下:

int main()
{
    //对stdout
    fprintf(stdout,"hello stdout!\n");
    const char* s1="hello 1!\n";
    write(1,s1,strlen(s1));//系统调用接口,fd=1,用于验证是否对应stdout

    printf("----------------\n");

    //对stdin
    int a=0;
    printf("stdin|输入整数a的值:");
    fscanf(stdin,"%d",&a);
    printf("stdin|实际读取a=%d\n",a);

    printf("fd=0| 输入字符串:");
    fflush(stdout);
    char buffer[20]="";
    ssize_t ret=read(0,buffer,sizeof(buffer));//系统调用接口,fd=0,用于验证是否对应stdin
    if(ret>0)//read会返回读取到的实际值个数
    {
        buffer[ret]='\n';
        printf("fd=0|实际读取::%s\n",buffer);
    }

    return 0;
}

  
  验证结果如下:
在这里插入图片描述
  
  
  
  
  3)、说明0、1、2与默认的三个标准文件之间的关系(FILE和fd)
  在C语言中,我们曾学习了解过文件指针:

  每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE.

       FILE *fopen(const char *path, const char *mode);

  需要知道,FILE这类结构体是由C标准库提供的,若要对文件读写则需通过OS操作系统,那么这类库函数中需要封装系统调用接口,也就意味着,系统层面只认识文件描述符fd,那么上层语言库中FILE内一定封装有fd。
  
  相关验证查看:

//验证FILE内封装有fd
int main()
{
    printf("stdin:%d\n",stdin->_fileno);
    printf("stdout:%d\n",stdout->_fileno);
    printf("stderr:%d\n",stderr->_fileno);
    return 0;
}

在这里插入图片描述

  
  
  
  
  

1.4.2、对fd的理解

  问题一:一个进程可以打开多个文件吗?
  回答:进程要访问文件,就需要打开文件,一般而言,一个进程可以打开多个文件。
  
  问题二:内核中是否需要管理打开的文件?如何管理文件?
  回答:需要。当操作系统中存在多个进程并且进程内部打开了文件,势必导致操作系统中存在大量被打开的文件。为了方便操作系统管理,对于这些文件则需要进行组织管理。
  说明:当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体,表示一个已经打开的文件对象。
在这里插入图片描述

  
  
  
  问题三:那么,进程和文件如何建立对应关系?
  进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files,指向一张表files_struct,该表最重要的部分就是包含一个指针数组,每个元素都是一个指向打开文件的指针!
在这里插入图片描述
  
  
  问题四:那么,文件描述符fd在内核中究竟是什么呢?
  如下述:fd实际为数组的下标(文件描述符表)。所以,只要拿着文件描述符,就可以找到对应的文件。同时也将进程管理与文件管理联系起来。
在这里插入图片描述
  
  
  
  

1.4.3、fd分配规则和重定向原理

1.4.3.1、原理演示

  1)、fd文件描述符分配规则说明
  结论:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
  
  验证结果如下:
在这里插入图片描述

  
  相关验证代码:

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

//验证文件描述符分配规则
int main()
{
    //close(0);//关闭stdin
    close(2);//关闭stderr
    int fd= open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

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

  
  
  
  2)、解释输出重定向
  上述代码演示了关闭stdin和stderr,假如我们关闭stdout,会产生什么结果?以下为相关演示:
在这里插入图片描述

  演示代码:

int main()
{
    close(1);//关闭stdout
    int fd= open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

    printf("fd:%d\n",fd);
    printf("fd:%d\n",fd);
    printf("fd:%d\n",fd);
    printf("fd:%d\n",fd);
    
    printf("hello stdout!\n");
    fprintf(stdout,"hello stdout.\n");
    const char* str="hello stdout~\n";
    fwrite(str,strlen(str),1,stdout);

    fflush(stdout);
    close(fd);
    return 0;
}

  可以看到原先输出到显示器上的内容,现如今输出到了文件中。为什么会产生这个现象?
在这里插入图片描述
  由此,就形成了输出重定向。实际上,重定向就是在OS内部,更改fd对应的内容指向。
  
  
  
  2)、解释追加重定向
  同理可得追加重定向,只是open中打开模式做了变动。
在这里插入图片描述

  
  相关代码如下:

[wj@VM-4-3-centos T0730]$ make
gcc -o myfile.o myfile.c
[wj@VM-4-3-centos T0730]$ ls
makefile  myfile.c  myfile.o
[wj@VM-4-3-centos T0730]$ ./myfile.o 
[wj@VM-4-3-centos T0730]$ ls
log.txt  makefile  myfile.c  myfile.o
[wj@VM-4-3-centos T0730]$ cat log.txt 
fd:1
hello stdout!
hello stdout.
hello stdout~
-------------------
[wj@VM-4-3-centos T0730]$ ./myfile.o 
[wj@VM-4-3-centos T0730]$ ./myfile.o 
[wj@VM-4-3-centos T0730]$ cat log.txt 
fd:1
hello stdout!
hello stdout.
hello stdout~
-------------------
fd:1
hello stdout!
hello stdout.
hello stdout~
-------------------
fd:1
hello stdout!
hello stdout.
hello stdout~
-------------------
[wj@VM-4-3-centos T0730]$ 

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

//演示追加重定向
int main()
{
    close(1);//关闭stdout
    int fd= open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

    printf("fd:%d\n",fd);
    
    printf("hello stdout!\n");
    fprintf(stdout,"hello stdout.\n");
    const char* str="hello stdout~\n";
    fwrite(str,strlen(str),1,stdout);
    printf("-------------------\n");

    fflush(stdout);
    close(fd);
    return 0;
}

  
  
  
  3)、解释输入重定向
  原理相同,只是此处open以读的方式打开,且关闭的是fd=0。
在这里插入图片描述

  相关代码如下:

//演示输入重定向
int main()
{
    close(0);
    int fd=open("log.txt",O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

    printf("fd:%d\n",fd);

    char buffer[360];
    fgets(buffer,sizeof buffer,stdin);
    printf("%s\n",buffer);
    
    return 0;
}

[wj@VM-4-3-centos T0730]$ ls
log.txt  makefile  myfile.c  myfile.o
[wj@VM-4-3-centos T0730]$ cat log.txt 
A New Day Has Come..

fd:1
hello stdout!
hello stdout.
hello stdout~
-------------------
fd:1
hello stdout!
hello stdout.
hello stdout~
-------------------
fd:1
hello stdout!
hello stdout.
hello stdout~
-------------------
[wj@VM-4-3-centos T0730]$ ./myfile.o 
fd:0
A New Day Has Come..


  
  
  

1.4.3.2、常规写法

  实际上,我们无需向上述一样打开文件、关闭文件,这里有相关的函数可使用man dup

NAME
       dup, dup2, dup3 - duplicate a file descriptor

SYNOPSIS
       #include <unistd.h>

       int dup(int oldfd);
       int dup2(int oldfd, int newfd);


  这里主要介绍dup2:关闭原先newfd对应的文件,并将oldfd赋值给newfd。

在这里插入图片描述

       dup2()  makes  newfd  be  the copy of oldfd, closing newfd first if necessary, but
       note the following:

       *  If oldfd is not a valid file descriptor, then the call fails, and newfd is  not
          closed.

       *  If  oldfd  is  a  valid file descriptor, and newfd has the same value as oldfd,
          then dup2() does nothing, and returns newfd.

       After a successful return from one of these system calls, the  old  and  new  file
       descriptors  may  be  used  interchangeably.   They  refer  to  the same open file
       description (see open(2)) and thus share file offset and file  status  flags;  for
       example,  if  the file offset is modified by using lseek(2) on one of the descrip‐
       tors, the offset is also changed for the other.

       The two descriptors do not share file descriptor flags (the  close-on-exec  flag).
       The  close-on-exec flag (FD_CLOEXEC; see fcntl(2)) for the duplicate descriptor is
       off.

  
  相关演示:
在这里插入图片描述

  
  演示代码:

//演示dup2的使用:
//需要:myfile XXXX ,在命令行运行./myfile.o程序,能够把后面的内容打印到文件中
int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        printf("argc error!\n");
        return 1;
    }

    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if(fd < 0)
    {
        perror("open");
        return 2;
    }
   
    dup2(fd,1);//关闭fd=1,即stdout,并将当前打开文件的fd内容复制给fd=1
    fprintf(stdout,"%s\n",argv[1]);
    
    close(fd);

    return 0;
} 

  

[wj@VM-4-3-centos T0730]$ make
gcc -o myfile.o myfile.c
[wj@VM-4-3-centos T0730]$ ls
makefile  myfile.c  myfile.o

[wj@VM-4-3-centos T0730]$ ./myfile.o GOOD_WORKS.
[wj@VM-4-3-centos T0730]$ ls
log.txt  makefile  myfile.c  myfile.o
[wj@VM-4-3-centos T0730]$ cat log.txt 
GOOD_WORKS.

[wj@VM-4-3-centos T0730]$ ./myfile.o use_it_or_lose_it.
[wj@VM-4-3-centos T0730]$ cat log.txt
use_it_or_lose_it.

[wj@VM-4-3-centos T0730]$ ./myfile.o man_is_mortal.
[wj@VM-4-3-centos T0730]$ cat log.txt
man_is_mortal.

  同理可得追加重定向、输入重定向。
  
  
  

1.4.3.3、重新理解Linux下一切皆文件

  将底层寻找其共同规律,使用结构体对象struct file统一管理起来。这样虽然底层的硬件不同, 对应的操作方法不同,但在上层角度,每个设备的核心访问函数都是一样的,read、wirte、I/O等等,只是其内部具体实现不同。
  上层整体看到的是struct file结构,这是完全一致的,故而可一视同仁,将所有硬件视为文件对待。
在这里插入图片描述
  这样的设计方案称之为VFS虚拟文件系统。
  
  
  
  
  
  

1.4.4、解释缓冲区

1.4.4.1、问题一:什么是缓冲区?谁提供的?

  说明:缓冲区实际上是一段内存空间,用于临时存储数据。
  
  
  验证代码如下:

int main()
{
    //使用C语言提供的接口
    printf("hello printf\n");
    fprintf(stdout,"hello fprintf\n");
    const char* s="hello fputs\n";
    fputs(s,stdout);

    //使用系统提供的接口
    const char* s2="hello write\n";
    write(1,s2,strlen(s2));

    //fork();//创建子进程
    return 0;
}

  验证结果入下:可以初步判断,缓冲区是有语言层面提供的。那么,为什么fork存在影响?缓冲区在这里做了什么?(相关回答见下述小节三)
在这里插入图片描述

  
  
  

1.4.4.2、问题二:为什么要缓冲区?

  1)、写透模式和写回模式
  在用户角度,缓冲区的一个意义在于方便。在系统层面,缓冲区可以提高整机效率,从而提高用户的影响速度。
在这里插入图片描述

  如上图,写透模式(WT)下存在一定问题,因此引入缓冲区,从而能提高整体效率,而后者称之为写回模式(WB)。
  
  
  2)、缓冲区刷新策略

  正常情况下:
   1、立即刷新
   2、行刷新(行缓冲):\n
   3、满缓冲(全缓冲)
  
  特殊情况:
   1、用户强中刷新:fflush
   2、进程退出
  
  PS:根据小节一,缓冲区由上层语言提供,对应的,它也需要维护缓冲区的刷新策略。
  
  
  3)、对缓冲区的认识

  说明: 一般而言,所有设备倾向于全缓冲,在缓冲区填满后才一次性刷新。这样可以减少IO次数,更少的访问外设,以便提高效率。(PS:OS和外设进行IO操作时,影响效率的主要矛盾不是传输数据量的大小,而是IO过程本身。)虽然如此,但缓冲区具体刷新策略要结合实际情况做妥协。

  常见情况说明: 行缓冲设备文件为显示器,全缓冲设备文件为磁盘。
  
  
  
  

1.4.4.3、问题三:缓冲区在哪?

  1)、问题解释

//验证缓冲区提供者
int main()
{
    //使用C语言提供的接口
    printf("hello printf\n");
    fprintf(stdout,"hello fprintf\n");
    const char* s="hello fputs\n";
    fputs(s,stdout);

    //使用系统提供的接口
    const char* s2="hello write\n";
    write(1,s2,strlen(s2));

    //fork();//创建子进程
    return 0;
}

  在小节一中曾遗留下一个问题,即为什么fork创建子进程后,向显示器输出和向文件中输出,获取结果会不同?
  
在这里插入图片描述
  1、对fork,首先要明确的是,fork创建子进程,虽然代码执行完成,但并不代表该串代码内涉及到的数据已经刷新。那么不进行进程替换时,子进程的代码数据来源于父进程,将文件刷新的过程是一种写入的过程,发生写时拷贝,子进程将获得自己的一份数据。
  
  2、使用库函数的文件函数,进程会将数据交给缓冲区,缓冲区内数据再转交给内核结构。
  
  3、向显示器打印,缓冲区的刷新策略是行刷新,上述打印内容均以\n结尾,满足行刷新策略。执行fork时,一定是函数执行完成,且缓冲区内数据也刷新完成。所以此时即使子进程写时拷贝,缓冲区内也无相关数据。
  
  4、使用重定向向磁盘中打印,即使有\n,但实则执行的是满刷新(全缓冲)。fork执行时,虽然函数已经执行完成,但被打印数据保存在缓冲区中,该数据属于父进程数据,子进程发生写时拷贝时,也会一并复制一份。
  
  5、此处演示案例中,fork之后无后续代码执行,那么面临进程退出,进程退出会使得缓冲区刷新,因此父子进程各自刷新其数据。
  
  
  2)、说明
  1、实际上,语言库为我们提供的是用户级缓冲区,实际操作系统层面还有一个内核级缓冲区。
  2、先前内容我们知道了struct FILE结构内部封装了fd,实际上其中还封装有该文件fd所对应的语言层面的缓冲区结构。
  
  
  
  
  
  
  
  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/825259.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

企业服务器中了Locked勒索病毒后怎么办,如何解决问题并提高防范意识

科学技术的发展给我们的生活带来了极大便利&#xff0c;但也为企业带来了安全威胁。近期&#xff0c;我们收到很多企业的求助&#xff0c;企业的服务器中了locked后缀勒索病毒&#xff0c;计算机上的所有文件都被加密&#xff0c;无法被正常调取&#xff0c;严重影响了企业的正…

使用预训练 ViT 模型的图像字幕中的视觉变换器 (ViT)-附源码

介绍 使用预训练 ViT 模型的图像字幕可以被视为图像下方的文本或书面描述,旨在提供图像细节的描述。它将图像翻译成文本描述的任务。它是通过连接视觉(图像)和语言(文本)来完成的。在本文中,我们使用图像中的 Vision Transformers (ViT) 作为使用 PyTorch 后端的主要技术…

2023牛客暑假多校-5-I-The Yakumo Family

解法&#xff1a;考虑枚举这个区间&#xff0c;设这个区间异或和为w。对于左端点 &#xff0c;预处理出其左侧的所有子区间异或和的和 ,表示区间上所有子区间异或和。对右侧也做同样操作预处理出&#xff0c;表示区间上所有子区间异或和。那么最后答案就是。最重要的就是如何求…

Linux下 Docker容器引擎基础(2)

目录 创建私有仓库 将修改过的nginx镜像做标记封装&#xff0c;准备上传到私有仓库 将镜像上传到私有仓库 从私有仓库中下载镜像到本地 CPU使用率 CPU共享比例 CPU周期限制 CPU 配额控制参数的混合案例 内存限制 Block IO 的限制 限制bps 和iops 创建私有仓库 仓库&a…

【数据结构|二叉树遍历】递归与非递归实现前序遍历、中序遍历、后序遍历

递归与非递归实现二叉树的前序遍历、中序遍历、后序遍历。 二叉树图 定义 前序遍历&#xff08;Preorder Traversal&#xff09;&#xff1a; 前序遍历的顺序是先访问根节点&#xff0c;然后按照先左后右的顺序访问子节点。对于上面的二叉树&#xff0c;前序遍历的结果是&…

IO流中「线程」模型总结

一、基础简介 在IO流的网络模型中&#xff0c;以常见的「客户端-服务端」交互场景为例&#xff1b; 客户端与服务端进行通信「交互」&#xff0c;可能是同步或者异步&#xff0c;服务端进行「流」处理时&#xff0c;可能是阻塞或者非阻塞模式&#xff0c;当然也有自定义的业务…

12.其他事件

12.1 页面加载事件 加载外部资源&#xff08;如图片、外联CSS和JavaScript等&#xff09;加载完毕时触发的事件 1.事件名&#xff1a;load ●监听页面所有资源加载完毕&#xff1a; ➢给window添加load事件 //页面加载事件 window.addEventListener( load, function () { //…

java后端富文本转word,再传递到浏览器下载。

思路参考&#xff0c;以及所有的工具类都使用了》牧羊人大佬的代码《 有帮助的话不用给到我点赞&#xff0c;给大佬点赞即可 这是前端代码&#xff0c;必须使用get。 post后端返回的流浏览器接收不到&#xff08;具体原因不详&#xff09;。get无法传递requestBody&#xff0c;…

Python实现GA遗传算法优化BP神经网络分类模型(BP神经网络分类算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 遗传算法&#xff08;Genetic Algorithm&#xff0c;GA&#xff09;最早是由美国的 John holland于20世…

STM32F4_内存管理(Malloc、Free)

目录 前言 1. 内存管理介绍 1.1 分块式内存管理 2. 实验程序 2.1 main.c 2.2 Malloc.c 2.3 Malloc.h 前言 相信大家在学习C语言的过程中&#xff0c;都会学习到 malloc 动态开辟函数和 free 释放内存函数&#xff1b;这两个函数带给我们的优越性是&#xff1a; 我们在使…

[深度学习] GPU处理能力(TFLOPS/TOPS)

计算能力换算 理论峰值 &#xff1d; GPU芯片数量GPU Boost主频核心数量*单个时钟周期内能处理的浮点计算次数 只不过在GPU里单精度和双精度的浮点计算能力需要分开计算&#xff0c;以最新的Tesla P100为例&#xff1a; 双精度理论峰值 &#xff1d; FP64 Cores &#xff0a;…

Scratch Blocks自定义组件之「下拉图标」

一、背景 由于自带的下拉图标是给水平布局的block使用&#xff0c;放在垂直布局下显得别扭&#xff0c;而且下拉选择后回修改image字段的图片&#xff0c;这让我很不爽&#xff0c;所以在原来的基础上稍作修改&#xff0c;效果如下&#xff1a; 二、使用说明 &#xff08;1&am…

转机来了,国内全新芯片技术取得突破,关键驱动引擎开始提速

芯片技术转机来了 我们都知道&#xff0c;芯片技术是现代信息技术的基石&#xff0c;它驱动着计算机、智能手机、物联网设备等各类电子设备的运行。 科技的不断进步&#xff0c;芯片技术也在不断演进。 从传统的集成电路到现代的微处理器和系统芯片&#xff0c;其计算能力和能…

Total Variation loss

Total Variation loss 适合任务 图像复原、去噪等 处理的问题 图像上的一点点噪声可能就会对复原的结果产生非常大的影响&#xff0c;很多复原算法都会放大噪声。因此需要在最优化问题的模型中添加一些正则项来保持图像的光滑性&#xff0c;图片中相邻像素值的差异可以通过…

Pytorch深度学习框架入门

1.pytorch加载数据 唤醒指定的python运行环境的命令&#xff1a; conda activate 环境的名称 from torch.utils.data import Dataset #Dataset数据处理的包 from PIL import Image import os#定义数据处理的类 class MyData(Dataset):#数据地址处理方法def __init__(self,ro…

从《信息技术服务数据中心业务连续性等级评价准则》看数据备份

​​​​​​​ 5月23日&#xff0c;国家标准化管理委员会与国家市场监督管理总局发布了《信息技术服务数据中心业务连续性等级评价准则》&#xff0c;旨在适应各行各业逐步深入的数字化转型&#xff0c;提升全社会对数据中心服务中断风险的重视。 信息技术服务数据中心业务连续…

KL15 是什么?ACC,crank,on等

KL含义 KL is the abbreviation for klemme which is the German term for connector / connection.KL是“ klemme”的缩写&#xff0c;这是德语中连接器或连接的术语。 KL30 &#xff0c;通常表示电瓶的正极。positive KL31&#xff0c;通常表示电瓶的负极。negative KL15, 通…

【NLP概念源和流】 04-过度到RNN(第 4/20 部分)

接上文 【NLP概念源和流】 03-基于计数的嵌入,GloVe(第 3/20 部分) 一、说明 词嵌入使许多NLP任务有了显著的改进。它对单词原理图的理解以及将不同长度的文本表示为固定向量的能力使其在许多复杂的NLP任务中非常受欢迎。大多数机器学习算法可以直接应用于分类和回归任务的…

go初识iris框架(三) - 路由功能处理方式

继了解get,post后 package mainimport "github.com/kataras/iris/v12"func main(){app : iris.New()//app.Handle(请求方式,url,请求方法)app.Handle("GET","/userinfo",func(ctx iris.Context){path : ctx.Path()app.Logger().Info(path) //获…

MTS性能监控你知道多少

前言 说到MySQL的MTS&#xff0c;相信很多同学都不陌生&#xff0c;从5.6开始基于schema的并行回放&#xff0c;到5.7的LOGICAL_CLOCK支持基于事务的并行回放&#xff0c;这些内容都有文章讲解&#xff0c;在本篇文章不再赘述。今天要讲的是&#xff0c;你知道如何查看并行回放…