【linux】:linux下文件的使用以及文件描述符(下)

简介: 【linux】:linux下文件的使用以及文件描述符(下)

二、文件描述符



1.0 & 1 & 2


通过对open函数的学习,我们知道了文件描述符就是一个小整数。linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2。0 1 2对应的物理设备一般是:键盘,显示器,显示器,所以输出还可以有以下方式:

下面我们用文件的读写完成简单的输入输出:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
  char buf[1024];
  ssize_t s = read(0,buf,sizeof(buf));
  if (s>0)
  {
    buf[s] = 0;
    write(1,buf,strlen(buf));
    write(2,buf,strlen(buf));
  }
  return 0;
}


1和2代表标准输出和标准错误,我们先去读一下缓冲区,当返回值是缓冲区的最后一个字符,我们在这个位置放入\0,然后写入刚刚读取到的字符,下面我们运行一下:

47b5b5784b704676aeda8962fb08b2cb.png

通过这个小例子我们就知道了文件描述符就是从0开始的小整数,当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件,于是就有了file结构体,表示一个已经打开的文件对象,而进程执行open系统调用,所以必须让进程和文件关联起来,每个进程都有一个指针*files,指向一张表files_struct,该表最重要的部分就是包含一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。


文件描述符的分配规则


我们写一个只读的代码来获取文件的fd:

int main()
{
  int fd = open("my.txt",O_RDONLY);
  if (fd<0)
  {
    perror("open");
    return 1;
  }
  printf("fd:%d\n",fd);
  close(fd);
  return 0;
}


ca683570a2d34bda840101b991def3a4.png 我们发现fd是3,那么这个时候我们把0关闭了再看看是什么结果:

int main()
{
  close(0);
  int fd = open("my.txt",O_RDONLY);
  if (fd<0)
  {
    perror("open");
    return 1;
  }
  printf("fd:%d\n",fd);
  close(fd);
  return 0;
}


04da7ea731724a0fb444113ae6b9bd55.png


通过运行结果我们发现,当我们将0关闭后文件描述符立马变成了0,我们再看看把2关闭了如何:

int main()
{
  //close(0);
  close(2);
  int fd = open("my.txt",O_RDONLY);
  if (fd<0)
  {
    perror("open");
    return 1;
  }
  printf("fd:%d\n",fd);
  close(fd);
  return 0;
}


ec82e3f6f34a42588786d6d5f60f46bf.png


当我们将2关闭后文件描述符又变成了2,这就说明文件描述符的分配规则是:在file_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。


2.重定向


还是刚刚的代码,如果我们将1关闭了会出现什么情况呢?

int main()
{
  //close(0);
  close(1);
  int fd = open("my.txt",O_WRONLY | O_CREAT,00644);
  if (fd<0)
  {
    perror("open");
    return 1;
  }
  printf("fd:%d\n",fd);
  fflush(stdout);
  close(fd);
  exit(0);
}


ca0bd90ac21443b88a3f895cf31fd55c.png


通过运行结果我们可以发现,本来应该输出到显示器上的内容,输出到了文件中,其中fd=1,这种现象叫做输出重定向,常见的重定向有> >> <。那么重定向的本质是什么呢?我们看下图:


e03cac692e2e4c45a1bdec2d05d94bea.png


当然对于重定向来说还有一个系统调用接口,这个函数叫dup2:

1. #include <unistd.h>
2. int dup2(int oldfd, int newfd);


对于这个函数的参数1来说是被换的那个描述符,参数2是像0,1,2,这样的默认描述符,接下来我们使用这个函数完成以下重定向:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main()
{
  int fd = open("my.txt",O_WRONLY | O_CREAT,00644);
  if (fd<0)
  {
    perror("open");
    return 1;
  }
  dup2(fd,1);
  printf("fd:%d\n",fd);
  close(fd);
}


a3c84da75e0748109c7702f3690d4404.png

通过上图我们可以看到成功的重定向到文件中。下面我们看看文件缓冲区的概念:

我们先编写一段代码看看现象:

int main()
{
  //c库中的
  fprintf(stdout,"hello fprintf\n");
  //系统调用
  const char* msg = "hello write\n";
  write(1,msg,strlen(msg));
  //fork();
  return 0;
}

5dedd5dd9bdf43f08646b979766a759e.png

可以正常打印,那么我们调用一下子进程会变成什么呢?

269261c8d26a4f9d8e665a2273f42e1c.png8cced6275c8a430d81155683837ca885.png

我们发现运行结果和刚刚是一样的,难道没有什么用吗?我们接下来重定向到文件中:

88fdddb8526441c58b93c3215debcda3.png


这个时候我们发现了,居然会多打印一个fprintf,为什么会出现这样的现象呢?


一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。

printf fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据

的缓冲方式由行缓冲变成了全缓冲。

而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后

但是进程退出之后,会统一刷新,写入文件当中。

但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的 一份数据,随即产生两份数据。

write 没有变化,说明没有所谓的缓冲

所以:


printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。 那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。

行缓冲:碰到\0就刷新缓冲区

全缓冲:缓冲区满了才刷新

显示器采用的刷新策略:行缓冲。普通文件采用的刷新策略:全缓冲

那么缓冲区在哪里呢?在你进行fopen打开文件的时候,你会得到FILE结构体,缓冲区就在这个FILE结构体中。


总结



对于linux下的文件操作而言,C语言等对于文件操作的函数都是经过linux系统文件接口来封装的,我们在用C语言文件操作的时候看着很简单的一句代码在系统调用中会有很多的操作,对于文件描述符实际上就是file_struct中的指针数组的下标,文件描述符的分配规则就是优先从最小的下标开始分配

目录
相关文章
|
1月前
|
Linux Shell 网络安全
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
本指南介绍如何利用 HTA 文件和 Metasploit 框架进行渗透测试。通过创建反向 shell、生成 HTA 文件、设置 HTTP 服务器和发送文件,最终实现对目标系统的控制。适用于教育目的,需合法授权。
72 9
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
|
22天前
|
Ubuntu Linux Go
golang编译成Linux可运行文件
本文介绍了如何在 Linux 上编译和运行 Golang 程序,涵盖了本地编译和交叉编译的步骤。通过这些步骤,您可以轻松地将 Golang 程序编译成适合 Linux 平台的可执行文件,并在目标服务器上运行。掌握这些技巧,可以提高开发和部署 Golang 应用的效率。
166 14
|
21天前
|
存储 NoSQL Linux
linux积累-core文件是干啥的
核心文件是Linux系统在程序崩溃时生成的重要调试文件,通过分析核心文件,开发者可以找到程序崩溃的原因并进行调试和修复。本文详细介绍了核心文件的生成、配置、查看和分析方法
61 6
|
23天前
|
存储 NoSQL Linux
linux之core文件如何查看和调试
通过设置和生成 core 文件,可以在程序崩溃时获取详细的调试信息。结合 GDB 等调试工具,可以深入分析 core 文件,找到程序崩溃的具体原因,并进行相应的修复。掌握这些调试技巧,对于提高程序的稳定性和可靠性具有重要意义。
143 6
|
2月前
|
Linux 开发工具 Perl
在Linux中,有一个文件,如何删除包含“www“字样的字符?
在Linux中,如果你想删除一个文件中包含特定字样(如“www”)的所有字符或行,你可以使用多种文本处理工具来实现。以下是一些常见的方法:
48 5
|
2月前
|
安全 Linux 数据安全/隐私保护
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。本文介绍了使用 `ls -l` 和 `stat` 命令查找文件所有者的基本方法,以及通过文件路径、通配符和结合其他命令的高级技巧。还提供了实际案例分析和注意事项,帮助读者更好地掌握这一操作。
59 6
|
2月前
|
Linux
在 Linux 系统中,`find` 命令是一个强大的文件查找工具
在 Linux 系统中,`find` 命令是一个强大的文件查找工具。本文详细介绍了 `find` 命令的基本语法、常用选项和具体应用示例,帮助用户快速掌握如何根据文件名、类型、大小、修改时间等条件查找文件,并展示了如何结合逻辑运算符、正则表达式和排除特定目录等高级用法。
189 6
|
2月前
|
监控 Linux Perl
Linux 命令小技巧:显示文件指定行的内容
在 Linux 系统中,处理文本文件是一项常见任务。本文介绍了如何使用 head、tail、sed 和 awk 等命令快速显示文件中的指定行内容,帮助你高效处理文本文件。通过实际应用场景和案例分析,展示了这些命令在代码审查、日志分析和文本处理中的具体用途。同时,还提供了注意事项和技巧,帮助你更好地掌握这些命令。
279 4
|
2月前
|
网络协议 Linux
linux系统重要文件目录
本文介绍了Linux系统中的重要目录及其历史背景,包括根目录、/usr、/etc、/var/log和/proc等目录的结构和功能。其中,/etc目录下包含了许多关键配置文件,如网卡配置、DNS解析、主机名设置等。文章还详细解释了各目录和文件的作用,帮助读者更好地理解和管理Linux系统。
72 2
|
2月前
|
缓存 监控 Linux