freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

文件描述符和特殊IO
2021-11-16 18:07:07

IO东西有点多,还没有学完。这次就借着IO来说一下文件描述符,还有一些特殊的IO。

文件描述符是什么?

文件描述符其实本身是一个索引表。以数组的形式做成的一个索引表。操作系统中,维护了三个数据结构。

首先是每个进程都有一个的文件描述符表。表内存放的就是文件描述符的相关信息。这些相关信息包括:

  • 控制文件描述符操作的一组标志
  • 对打开文件句柄的引用

那么我们现在可以构想一下这个文件描述符表。它大概是一个结构体数组。我们得到的文件描述符都是数字,所以我们其实是得到这个数组的下标了。如果画一下的话,大概是这个样子image.png
先不要去管结构体内存放的具体内容。我会慢慢的讲解。

然后第二个数据结构被称为打开文件表。表中的每一项都被称为打开文件句柄。我们回过头去看文件描述符表中的第二个成员,其实就是相关打开文件句柄或者是其地址。

每个文件打开句柄都有以下成员:

  • 当前的文件偏移量(文件位置指针距离文件头的距离)
  • 打开文件的时候的状态标志(打开文件时候设置的flag参数)
  • 文件的访问模式(只读、只写、读写)
  • 与信号驱动IO相关的设置
  • 对相应文件inode对象的引用

也就是说我们的flags、文件访问模式、文件偏移都被记录了下来,并且打开文件句柄又指向了另外一个数据结构inode

接下来就该讲解inode,inode和文件系统息息相关。每个文件系统都会为该系统上含有的所有文件建立一个inode表。每个文件的inode信息如下:

  • 文件类型(Linux上面输入ls -l指令可以看到)
  • 一个指针,该指针指向文件所持有的锁的列表
  • 文件的属性(属性有很多,这里先不展开讲)

一幅图来表示以下文件描述符表、打开文件表、inode表之间的关系
image.png
同时还有几个要点注意以下:

  • 两个不同的文件描述符,如果指向同一个打开文件句柄,将会共享同一文件偏移量。如果通过一个文件描述符修改了文件偏移量,通过另外一个文件描述符是可以看出来的。这和两个不同的文件描述符是否属于同一个进程无关。
  • 要获取和修改打开文件的标志,通过一个文件描述符修改,通过另外一个文件描述符可以看到。与两个不同的文件描述符是否属于同一个进程无关。
  • 无论两个不同的文件描述符是否属于同一个进程,修改一个文件描述符的控制标志,通过另外一个文件描述符无法看到。因为文件描述符标志是每个文件描述符的私有成员。

还有一个问题,文件描述符标志到底是什么?经过我的查找,文件描述符标志是一个被称为"close-on-exec"的标志。见名知意,这个表示是执行时关闭。

当我们执行fork的时候,一个进程会衍生出它的子进程。它的子进程会继承它的堆和栈(简单理解就是复制,其实不仅仅是复制,而是写时复制。先不展开讲)。包括父进程的打开文件也会继承。

假如说此时父进程已经打开了很多的文件,我们又需要执行exec函数。此时就会面临一个尴尬:如果我在子进程出现的时候关闭一些无用的进程,由于打开的文件太多所以是非常麻烦的一件事;如果不进行关闭,程序执行exec后就没有机会进行关闭了,因为内存中的数据全部被刷新掉了,造成了资源的浪费。于是该标志就出现了。

我们在父进程中就可以直接设置该标志,设置该标志以后我们就可以fork出子进程的时候自动关闭该文件。

小总结:我们的获取到文件描述符本身是引用了打开文件表,而打开文件表最后也会指向inode表。另外我们还了解到了文件描述符的控制标志就是执行时关闭标志,为了节约计算机资源而设置的一个标志。

深入理解重定向

Linux中有重定向符,分为输入重定向和输出重定向。那么什么是重定向?我们都知道linux中一切皆文件,硬件设备在linux中也是文件。普通的输入和输出不过就是把数据从一个文件传输到另一个文件。

我们回过头看文件描述符相关的三个表,我们会发现我们得到的数字就是文件描述符表的相关下标。linux通过数字从而找到相应的文件描述符。想要对文件做修改,最终还是得落实到文件。因此文件描述符中有一个成员指向打开文件表,然后打开文件表又有一个成员指向了inode表。

因此我们可以知道,文件描述符和真实文件之间有一条纽带也就是那个指针。也就是说我们通过更改指针的指向就可以将一个文件描述符指向其它的文件。首先来看一个例子image.png
首先创建一个文件名字叫做re,然后输出"hello world",然后将输出重定向到re文件(输出重定向不写文件描述符默认为1)。其实本质上就是将文件描述符的指针给修改到了文件re上。更准确的来说,重定向符其实是将文件描述符的一个元素赋值给另一个元素。我们假设文件描述符表为FD。那么这个输出重定向就相当于做了这么一个操作FD[1] = FD[3](假设re在文件描述符里面是3)

当我们使用重定向符的时候,其实linux系统会首先打开re文件,并且将re文件的各个信息放入到打开文件表中去。然后在FD数组中创建一个re的文件描述符,最后将其赋值给FD[1]。
由此我们可以知道,重定向符应该叫做文件描述符操作符。

关于重定向符其实还有一个这样的操作./conmand>file 2>&1。这样其实就是将标准输出和标准报错全都重定向到文件file中。前面应该没有疑问,有疑问的应该是&符号。在大家的印象中这句指令应该这么写command>file 2>1,但是仔细观察应该会发现file是一个文件名,也就是说如果以后面这种写法就表示将标准错误重定向的文件名为1的文件描述符。加上&其实是解释1为文件描述符。另外需要大家注意,&符号仅在重定向上下文中才被解析为文件描述符。

讲完重定向就不得不讲两个函数,重定向不能没有它们
image.png
其中dup函数将会复制一个旧的文件描述符,并且返回一个新的文件描述符。两个文件描述符都指向同一个打开文件句柄。由于linux会分配可用的最小文件描述符,所以上面的重定向在代码层面的实现就是这样

oldfd = open("./re",O_RDWR|O_CREAT,0666);
close(1);
newfd = dup(oldfd);

想要重定向,只需要先关闭掉标准输出,然后利用dup创建新的文件描述符。所有输出函数输出的位置都是标准输出。
那么这一句command>filename 2>&1如何实现?操作一样,只需要将1和2全部关闭就可以了。

其实还有一个更简单的操作,就是dup2。dup2将会把oldfd复制到newfd上面,如果newfd已经打开,那么会直接将其关闭以后再进行复制。这样就简单很多,但是关闭文件的所有错误都会被直接忽略,所以建议还是显式关闭比较好。

还有一个操作也可以模仿重定向,那就是fcntl函数。newfd = fcntl(oldfd,F_DUPFD,startfd),这个操作将会复制oldfd的文件描述符,将其复制到大于等于startfd并且还未使用的文件描述符上面。这里的具体代码就不写了,有兴趣的话可以自己写一下。

原子操作:在特定偏移处输入和输出

这次是一组原子操作,无法被打断。两个函数pread和pwrite。
image.png
这两个函数多了一个控制偏移的参数。根据当前位置指针的偏移,并且这两个函数不会改变文件位置指针。这两个函数主要用在多线程,多个线程共享同一个文件打开句柄的情况下会共享文件位置指针。可能造成

分散输出和集中输入

又是一组输入输出函数
image.png
这两个函数可以一次性对多个缓冲区进行读写操作。其中iovec是一个数据结构。

struct ioevc{
    void *iov_base;
    size_t iov_len
}

iov是iovec类型的数组。iovcnt则表示了iov数组元素的个数。

后面的函数先简单的了解一下就好,到了多线程会经常用到。

# linux # 系统编程 # c语言
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者