Linux

Linux,第1张

Linux

文章目录
    • 1.C语言文件IO
    • *当前路径的概念
    • 标准输入、标准输出、标准错误
    • 2.Linux系统文件IO
    • 打开文件open(sys/types.h - sys/stat.h - fcntl.h)
    • 关闭文件close(unistd.h)
    • *标志位(方便函数传参)
    • 从文件中读取文件read(unistd.h)
    • 向文件写入数据write(unistd.h)
    • 3.文件描述符(数组下标)
    • 内存文件VS磁盘文件
    • struct file结构体(内存文件)
    • 文件的构成(磁盘文件)
    • struct files_struct结构体
    • 4.文件描述符的分配规则
    • 5.输出重定向原理
    • C语言FILE*与文件描述符的关系
    • dup2函数调用实现重定向(fcntl.h / unistd.h)
    • 6.缓冲区
    • fflush(stdout)的原因

1.C语言文件IO *当前路径的概念

在C语言文件 *** 作时调用fopen函数以写的方式打开文件,会自动在当前路径下创建文件

#include

int main()
{
  FILE*fp=fopen("Test","w");
  fclose(fp);
  return 0;
}

注意:当前路径并不是指可执行程序的位置,而是当前的工作目录
eg:

标准输入、标准输出、标准错误

stdin:标准输入- - >键盘
stdout:标准输出- - >显示器
stderr:标准错误- - >显示器

每个进程在打开时都会默认打开这三个输入输出流

2.Linux系统文件IO 打开文件open(sys/types.h - sys/stat.h - fcntl.h)


两种初始化方式
参数解释:
pathname:要打开的文件名

flags:打开文件选项

  • O_APPEND:追加的方式打开文件
  • O_RDWR:读写方式打开文件
  • O_CREAT:当文件不存在时自动创建文件

mode:打开文件默认权限
注意:这个权限收到系统umask值的影响,umask在Linux-文件权限中介绍不在赘述

返回值:
成功返回文件描述符(file descriptor),失败返回-1

关闭文件close(unistd.h)


参数解释:
fd:文件描述符
返回值:成功返回0,失败返回-1并设置错误码

*标志位(方便函数传参)

一个整数有32位,每一个比特位代表一种标志,每一个标志通过 | 运算联系起来可以一起传入函数中

eg:
#define X 0x1;
00000000…1
#define Y 0x2;
0000000…10
X|Y就把
0000000…11传入,相当于把X和Y的信息一起传入函数中
在函数内部判断某一个比特位是否为1就代表是否传入这个信息

eg:

#include
#include
#include
#include
#include
int main()
{
  umask(0);//将umask值设为0
  int fd=open("Test.txt",O_WRONLY|O_CREAT,0666);
  printf("%dn",fd);
  close(fd);
  return 0;
}

从文件中读取文件read(unistd.h)


参数解释
fd:从那个文件描述符中读数据
buf:读到的数据放到那个缓冲区
count:每次要读几个字节数据
返回值
返回实际读到的字节个数
返回值<=count

向文件写入数据write(unistd.h)


参数解释
fd:写到那个文件描述符中。
buf:写那个缓冲区中的数据
count:要写多大的字节数

返回值:实际上写了几个字节。

eg:

#include
#include
#include
#include
#include
#include

int main()
{
  umask(0);//将umask值设为0
  int fd=open("Test.txt",O_RDWR|O_CREAT,0666);
  if(fd<0)
  {
    return -1;
  }
  char ch=0;
  while(1)
  {
    ssize_t size=read(fd,&ch,1);//每次读取一格字符
    if(size<=0){
      break;
    }
    write(1,&ch,1);//1文件描述符是标准输出(显示器)的文件描述符
  }
  close(fd);
  return 0;
}

3.文件描述符(数组下标)

在Linux中系统,默认一个进程会打开3个文件描述符
0,1,2、分别代表标准输入,标准输出,标准错误,对应C语言的(stdin,stdout,stderr)
这个数字本质是一个数组下标

内存文件VS磁盘文件 struct file结构体(内存文件)

Linux系统为了管理保存进程打开的文件,用struct file来描述每一个打开的文件,多个文件之间选择双链表的形式组织起来。这张双链表保存在内存中。
这种文件称为内存文件

文件的构成(磁盘文件)

一个文件不仅仅由文件的内容,还包括修改时间,文件大小等信息。这些信息统称为文件属性

所以:文件=文件内容+文件属性

这个文件称为磁盘文件

文件被打开时有两份,一份是加载到内存的内存文件,一份是磁盘文件。
内存文件:将磁盘文件中的属性信息加载到内存中,形成struct file数据结构。延后式加载数据(当进行文件 *** 作时才加载数据)。
每一个struct file结构体代表一个打开的文件。

struct files_struct结构体

每一个进程的task_struct这个结构体。
结构体中有结构struct file* fd_array[32]数组每个元素都是一个指向内存文件的指针


不同的进程有不同的files_struct但都指向同一张struct file双链表。文件描述符就是files_struct结构中fd_array数组的数组下标。

4.文件描述符的分配规则
#include
#include
#include
#include
#include
#include

int main()
{
  umask(0);
  int fd=open("a.txt",O_RDWR,0666);
  printf("fd=%dn",fd);
  close(fd);
  close(0);//关闭标准输出
  fd=open("a.txt",O_RDWR,0666);
  printf("New fd=%dn",fd);
  close(fd);
  return 0;
}


在进行文件描述符分配时:从上到下扫描,最小的但是没有被使用的位置开始分配

5.输出重定向原理

将要输出到显示器上的信息重定向到文件中

#include
#include
#include
#include
#include
#include

int main()
{
  umask(0);
  close(1);//关闭标准输出,fd的值为1
  int fd=open("a.txt",O_RDWR|O_CREAT,0666);
  write(1,"1n",2);
  write(1,"2n",2);
  close(fd);
  return 0;
}


只要是往显示器上打印的数据都会写到文件中

重定向的本质:
修改文件描述符fd下标对应的struct file*所指向的内容

C语言FILE*与文件描述符的关系

C语言中FILE是个结构体,内部封装了文件描述符

C语言中的stdout:
stdout在C语言中可以看作FILE*指针,指向的FILE结构体中的包含的文件描述符为1

stdin与stderr也类似

所以:

#include
#include
#include
#include
#include
#include

int main()
{
  umask(0);
  close(1);//关闭标准输出,此时fd的值为1
  int fd=open("a.txt",O_RDWR|O_CREAT,0666);
  fprintf(stdout,"Hello Linuxn");//因为关闭了1号文件描述符,所以改向文件中写入

  fflush(stdout);//刷新缓冲区
  close(fd);
  return 0;
}

此时stdout中fd=1指向的是新文件,达到了重定向

根据上述可知:
调用C语言的fopen函数时

  • 给调用用户申请FILE结构体,并且返回FILE*(结构体地址)
  • 在底层调用open函数打开文件,并将open函数的返回值(文件描述符)填充到FILE结构体中的fd上。

类似的输入重定向原理与输出重定向类似。

dup2函数调用实现重定向(fcntl.h / unistd.h)



将oldfd下标对应的fd_array数组里函数指针拷贝到newfd下标对应的fd_array数组里函数指针。
也就是说最后fd_array[newfd]=fd_array[oldfd]
eg:dup2(fd,1);
将1号文件描述符所对应的函数指针变成fd号文件描述符所对应的函数指针。
向标准输出写入的数据会被重定向到文件中。

#include
#include
#include
#include
#include
#include

int main()
{
  umask(0);
  int fd=open("a.txt",O_RDWR|O_CREAT,0666);
  if(fd<0){
    return -1;
  }
  dup2(fd,1);
  printf("Hello Linuxn");
  fflush(stdout);
  close(fd);
  return 0;
}

6.缓冲区

缓冲的分类

  • 无缓冲

  • 行缓冲:常见对显示器进行刷新数据。如C语言中的printf函数中的n刷新

  • 全缓冲:常见对文件读写时采用全缓冲。当把缓冲区内容写满才刷新缓冲区

eg:

#include
#include
#include
#include
#include
#include

int main()
{
  //C语言
  printf("Hello printfn");
  fprintf(stdout,"Hello fprintfn");

  //系统
  const char* mes="Hello writen";
  write(1,mes,strlen(mes));
  fork();
  return 0;
}


上述现象总结:

  1. 打印到显示器上是行缓冲,重定向到文件中是全缓冲。
  2. C语言接口在重定向到文件时打印了两次,系统接口重定向时打印了一次

原因:

  1. 当在显示器上打印时是行刷新,每次打印时n会刷新缓冲区,所以fork创建子进程时缓冲区里面没有打印的数据
  2. 重定向到文件时变成全缓冲,所以fork创建子进程时之前没有刷新缓冲区,子进程缓冲区里里面有打印的数据。程序退出时,进程具有独立性,父子进程刷新缓冲区,导致打印了两次。
  3. 系统调用没有缓冲区,只有printf和fprintf存在缓冲区,所以系统调用只打印了一次。

C库函数是对系统调用的封装。所以缓冲区是语言提供的。
这个缓冲区在内存中,C语言中FILE结构体中不仅有文件描述符,还包括缓冲区(用户缓冲区)

FILE中的缓冲区在用户区,刷新时先把用户区的缓冲区数据拷贝到内核缓冲区上,在刷新到显示器或磁盘上

fflush(stdout)的原因

再次来分析代码:

#include
#include
#include
#include
#include
#include

int main()
{
  umask(0);
  close(1);//关闭标准输出,此时fd的值为1
  int fd=open("a.txt",O_RDWR|O_CREAT,0666);
  fprintf(stdout,"Hello Linuxn");

  fflush(stdout);//刷新缓冲区
  close(fd);
  return 0;
}

之所以要fflush(stdout)的原因:
重定向文件后,语言缓冲模式变为全缓冲。如果最后不强制刷新的话,close函数将文件关闭,程序结束后,缓冲区里面的数据将没有办法刷新到文件中了。所以一定要强制刷新缓冲区

但如果用C语言的接口fclose时会自动刷新缓冲区,就不需要手动强制刷新缓冲区

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5691101.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存