避免被攻击:如何解决Linux文件描述符泄漏问题? (linux fd 泄漏)

Linux是一种广泛使用的操作系统,被许多公司和组织用于运行服务器和Web应用程序。然而,就像所有操作系统一样,Linux也存在安全漏洞,其中之一就是文件描述符泄漏问题。在本文中,我们将了解文件描述符泄漏问题的原因,并提供一些解决方案,以帮助Linux用户保护他们的系统免遭攻击。

什么是文件描述符?

在Linux中,文件描述符是内核给每个进程分配的一种正整数,用于识别和访问文件和设备。文件描述符在操作系统中起着非常重要的作用,因为它们允许进程与外部系统进行通信。例如,当您在Web浏览器中访问网站时,浏览器将打开一个文件描述符,以便通过网络访问服务器上的内容。

文件描述符泄漏问题是什么?

文件描述符泄漏指的是当进程打开一个文件描述符但不关闭它时发生的情况。如果发生泄漏,该进程将会占用系统资源,直至作系统或其他程序强制关闭。如果这种情况一直持续下去,系统将变得越来越慢,最终可能导致崩溃或系统瘫痪。

为什么文件描述符泄漏很危险?

除了导致系统崩溃或瘫痪外,文件描述符泄漏问题还会出现安全隐患。攻击者可以利用文件描述符泄漏漏洞来获得系统权限,这可能会使其能够访问敏感信息或执行恶意软件。因此,及早识别和解决文件描述符泄漏问题非常重要。

如何检测文件描述符泄漏?

要检测文件描述符泄漏,您可以使用各种工具,例如lsof、procfs、strace等。以下是一些检测文件描述符泄漏的命令:

$ lsof | grep deleted

此命令将显示所有已删除的文件,这些文件可能是由于文件描述符泄漏而没有被关闭的。

$ sudo ls -l /proc/[PID]/fd

此命令将显示进程[PID]的所有打开的文件描述符。如果该列表非常长而且包含大量未关闭的文件,则可能存在文件描述符泄漏。

$ strace -p [PID]

此命令将显示进程[PID]的所有系统调用和库函数调用。您可以使用此命令来查看进程的打开和关闭文件描述符的情况。

如何解决文件描述符泄漏问题?

以下是解决文件描述符泄漏问题的一些更佳实践:

1. 关闭不必要的文件描述符

打开文件描述符是一项资源密集型操作。因此,应尽可能少地打开文件描述符,同时及时关闭不再需要的文件描述符。例如,如果在 Web 应用程序中读取文件,则必须在读取完成后关闭该文件。

2. 使用文件描述符限制

Linux内核中有一些参数可以用来限制每个进程所能打开的文件描述符数量。您可以通过增加文件描述符限制来提高系统的安全性和可靠性。以下是一些常见的文件描述符限制参数:

nofile:限制进程使用文件描述符的更大数目。

nproc:限制单个用户可以创建的更大进程数。

3. 使用自动垃圾收集器

自动垃圾收集器(Automatic Garbage Collection,AGC)是一种内存管理技术,能够在运行时自动检测和释放不再使用的对象。例如,如果您的Web应用程序使用Java编写,那么您可以使用Java虚拟机(JVM)中内置的垃圾收集器来避免文件描述符泄漏问题。

结论

文件描述符泄漏是Linux操作系统中的一个安全隐患,可能会使攻击者获得系统权限或导致系统崩溃或瘫痪。通过正确设置文件描述符的限制、使用自动垃圾收集器和关闭不必要的文件描述符等更佳实践,可以有效地减少文件描述符泄漏的风险。同时,及早识别和解决文件描述符泄漏问题也是确保Linux系统安全和稳定运行的关键。

相关问题拓展阅读:

[转]浅谈Linux下的零拷贝机制

维基上是这么描述零拷贝的:零拷贝描述的是CPU不执行拷贝数据从一个存储区域到另一个存储区域的任务,这通常用于通过网络传输一个文件时以减少CPU周期和内存带宽。

减少甚至完全避免不必要的CPU拷贝,从而让CPU解脱出来去执行其他的任务

减少内存带宽的占用

通常零拷贝技术还能够减少用户空间和操作系统内核空间之间的上下文切换

从Linux系统上看,除了引导系统的BIN区,整个内存空间主要被分成两个部分:

内核空间(Kernel space)

用户空间(User space)

。“用户空间”和“内核空间”的空间、操作权限以及作用都是不一样的。

内核空间是Linux自身使用的内存空间,主要提供给程序调度、内存分配、连接硬件资源等程序逻辑使用;

用户空间则是提供给各个进程的主要空间。用户空间不具有访问内核空间资源的权限,因此如果应用程序需要使用到内核空间的资源,则需要通过系统调用来完成:从用户空间切换到内核空间,然后在完成相关操作后再从内核空间切换回用户空间。

① 直接 I/O:对于这种数据传输方式来说,应用程序可以直接访问硬件存储,操作系统内核只是辅助数据传输。这种方式依旧存在用户空间和内核空间的上下文切换,但是硬件上的数据不会拷贝滑仿一份到内核空间,而是直接拷贝至了用户空间,因此直接I/O不存在内核空间缓冲区和用户空间缓冲区之间的数据拷贝。

② 在数据传输过程中,避免数据在用户空间缓冲区和系统内核空间缓冲区之间的CPU拷贝,以及数据在系统内核空间内的CPU拷贝。本文主要讨论的就是该方式下的零拷贝机制。

③ copy-on-write(写时复制技术):在某些情况下,Linux操作系统的内核空间缓冲区可能被多个应用程序所共享,操作系统有可能会将用户空间缓冲区地址映射到内核空间缓存区中。当应用程序需要对共享的数据进行修改的时候,才需要真正地拷贝数据到应用程序的用户空间缓冲区中,并且对自己用户空间的缓冲区的数据进行修改不会影响到其他共享数据的应用程序。所以,如果应用程序不需要对数据进行任何修改的话,就不会存在数据从系统内核空间缓冲区拷贝到用户空间缓冲区的操作。

下面我们通过一个Java非常常见的应用场景:将系统中的文件发送到远端(该流程涉及:磁盘上文件 ——> 内存(字节数组) ——> 传输给用户/网络)来详细展开传统I/O操作和通过零拷贝来实现的I/O操作。

① 发出read系统调用:导致用户空间到内核空间的上下文切换(之一次上下文切换)。通过DMA引擎将文件中的数据从磁盘上读取到内核空间缓冲区(之一次陆迅拷贝: hard drive ——> kernel buffer)。

② 将内核空间缓冲区的数据拷贝到用户空间缓冲区(第二次拷贝: kernel buffer ——> user buffer),然后read系统调用返回。而系统调用的返回又会导致一次内核空间到用户空间的上下文切换(第二次上下文切换)。

③ 发出write系统调用:导致用户空间到内核空间的上下文切换(第三次上下文切换)。将用户空间缓冲区中的数据拷贝到内核空间中与socket相关联的缓冲区中(即,第②步中从内核空间缓冲区拷贝而来的数据原封不动的再次拷贝到内核空间的socket缓冲区中。)(第三次拷贝: user buffer ——> socket buffer)。

④ write系统调用返回,导致内核空间到用户空间的再次上下文切换(第四次上下文切换)。通过DMA引擎将内核缓冲区中的数据传递到协议引擎(第四次拷贝: socket buffer ——> protocol engine),这次拷贝是一个独立且异步的过程。

Q:

你可能会问独立和异步这是什么意思?难道是调用会在数据被传输前返回?

A:

事实上调用的返回并不保证数据被传输;它甚至不保证传输的开始。它只是意味着将我们要发送的数据放入到了一个待发送的队列中,在我们之前可能有许多数据包在排队。信悉纤除非驱动器或硬件实现优先级环或队列,否则数据是以先进先出的方式传输的。

总的来说,传统的I/O操作进行了4次用户空间与内核空间的上下文切换,以及4次数据拷贝。其中4次数据拷贝中包括了2次DMA拷贝和2次CPU拷贝。

Q:

传统I/O模式为什么将数据从磁盘读取到内核空间缓冲区,然后再将数据从内核空间缓冲区拷贝到用户空间缓冲区了?为什么不直接将数据从磁盘读取到用户空间缓冲区就好?

A:

传统I/O模式之所以将数据从磁盘读取到内核空间缓冲区而不是直接读取到用户空间缓冲区,是为了减少磁盘I/O操作以此来提高性能。因为OS会根据局部性原理在一次read()系统调用的时候预读取更多的文件数据到内核空间缓冲区中,这样当下一次read()系统调用的时候发现要读取的数据已经存在于内核空间缓冲区中的时候只要直接拷贝数据到用户空间缓冲区中即可,无需再进行一次低效的磁盘I/O操作(注意:磁盘I/O操作的速度比直接访问内存慢了好几个数量级)。

Q:

既然系统内核缓冲区能够减少磁盘I/O操作,那么我们经常使用的BufferedInputStream缓冲区又是用来干啥的?

A:

BufferedInputStream的作用是会根据情况自动为我们预取更多的数据到它自己维护的一个内部字节数据缓冲区中,这样做能够减少系统调用的次数以此来提供性能。

总的来说内核空间缓冲区的一大用处是为了减少磁盘I/O操作,因为它会从磁盘中预读更多的数据到缓冲区中。而BufferedInputStream的用处是减少“系统调用”。

DMA(Direct Memory Access) ———— 直接内存访问 :DMA是允许外设组件将I/O数据直接传送到主存储器中并且传输不需要CPU的参与,以此将CPU解放出来去完成其他的事情。

而用户空间与内核空间之间的数据传输并没有类似DMA这种可以不需要CPU参与的传输工具,因此用户空间与内核空间之间的数据传输是需要CPU全程参与的。所有也就有了通过零拷贝技术来减少和避免不必要的CPU数据拷贝过程。

① 发出sendfile系统调用,导致用户空间到内核空间的上下文切换(之一次上下文切换)。通过DMA引擎将磁盘文件中的内容拷贝到内核空间缓冲区中(之一次拷贝: hard drive ——> kernel buffer)。然后再将数据从内核空间缓冲区拷贝到内核中与socket相关的缓冲区中(第二次拷贝: kernel buffer ——> socket buffer)。

② sendfile系统调用返回,导致内核空间到用户空间的上下文切换(第二次上下文切换)。通过DMA引擎将内核空间socket缓冲区中的数据传递到协议引擎(第三次拷贝: socket buffer ——> protocol engine)

总的来说,通过sendfile实现的零拷贝I/O只使用了2次用户空间与内核空间的上下文切换,以及3次数据的拷贝。其中3次数据拷贝中包括了2次DMA拷贝和1次CPU拷贝。

Q:

但通过是这里还是存在着一次CPU拷贝操作,即,kernel buffer ——> socket buffer。是否有办法将该拷贝操作也取消掉了?

A:

有的。但这需要底层操作系统的支持。从Linux 2.4版本开始,操作系统底层提供了scatter/gather这种DMA的方式来从内核空间缓冲区中将数据直接读取到协议引擎中,而无需将内核空间缓冲区中的数据再拷贝一份到内核空间socket相关联的缓冲区中。

从Linux 2.4版本开始,操作系统底层提供了带有scatter/gather的DMA来从内核空间缓冲区中将数据读取到协议引擎中。这样一来待传输的数据可以分散在存储的不同位置上,而不需要在连续存储中存放。那么从文件中读出的数据就根本不需要被拷贝到socket缓冲区中去,只是需要将缓冲区描述符添加到socket缓冲区中去,DMA收集操作会根据缓冲区描述符中的信息将内核空间中的数据直接拷贝到协议引擎中。

① 发出sendfile系统调用,导致用户空间到内核空间的上下文切换(之一次上下文切换)。通过DMA引擎将磁盘文件中的内容拷贝到内核空间缓冲区中(之一次拷贝: hard drive ——> kernel buffer)。

② 没有数据拷贝到socket缓冲区。取而代之的是只有相应的描述符信息会被拷贝到相应的socket缓冲区当中。该描述符包含了两方面的信息:a)kernel buffer的内存地址;b)kernel buffer的偏移量。

③ sendfile系统调用返回,导致内核空间到用户空间的上下文切换(第二次上下文切换)。DMA gather copy根据socket缓冲区中描述符提供的位置和偏移量信息直接将内核空间缓冲区中的数据拷贝到协议引擎上(第二次拷贝: kernel buffer ——> protocol engine),这样就避免了最后一次CPU数据拷贝。

总的来说,带有DMA收集拷贝功能的sendfile实现的I/O只使用了2次用户空间与内核空间的上下文切换,以及2次数据的拷贝,而且这2次的数据拷贝都是非CPU拷贝。这样一来我们就实现了最理想的零拷贝I/O传输了,不需要任何一次的CPU拷贝,以及最少的上下文切换。

在linux2.6.33版本之前 sendfile指支持文件到套接字之间传输数据,即in_fd相当于一个支持mmap的文件,out_fd必须是一个socket。但从linux2.6.33版本开始,out_fd可以是任意类型文件描述符。所以从linux2.6.33版本开始sendfile可以支持“文件到文件”和“文件到套接字”之间的数据传输。

Q:

对于上面的第三点,如果我们需要对数据进行操作该怎么办了?

A:

Linux提供了mmap零拷贝来实现我们的需求。

mmap(内存映射)是一个比sendfile昂贵但优于传统I/O的方法。

① 发出mmap系统调用,导致用户空间到内核空间的上下文切换(之一次上下文切换)。通过DMA引擎将磁盘文件中的内容拷贝到内核空间缓冲区中(之一次拷贝: hard drive ——> kernel buffer)。

② mmap系统调用返回,导致内核空间到用户空间的上下文切换(第二次上下文切换)。接着用户空间和内核空间共享这个缓冲区,而不需要将数据从内核空间拷贝到用户空间。因为用户空间和内核空间共享了这个缓冲区数据,所以用户空间就可以像在操作自己缓冲区中数据一般操作这个由内核空间共享的缓冲区数据。

③ 发出write系统调用,导致用户空间到内核空间的上下文切换(第三次上下文切换)。将数据从内核空间缓冲区拷贝到内核空间socket相关联的缓冲区(第二次拷贝: kernel buffer ——> socket buffer)。

④ write系统调用返回,导致内核空间到用户空间的上下文切换(第四次上下文切换)。通过DMA引擎将内核空间socket缓冲区中的数据传递到协议引擎(第三次拷贝: socket buffer ——> protocol engine)

总的来说,通过mmap实现的零拷贝I/O进行了4次用户空间与内核空间的上下文切换,以及3次数据拷贝。其中3次数据拷贝中包括了2次DMA拷贝和1次CPU拷贝。

FileChannel中大量使用了我们上面所提及的零拷贝技术。

FileChannel的map方法会返回一个MappedByteBuffer。MappedByteBuffer是一个直接字节缓冲器,该缓冲器的内存是一个文件的内存映射区域。map方法底层是通过mmap实现的,因此将文件内存从磁盘读取到内核缓冲区后,用户空间和内核空间共享该缓冲区。

MappedByteBuffer内存映射文件是一种允许Java程序直接从内存访问的一种特殊的文件。我们可以将整个文件或者整个文件的一部分映射到内存当中,那么接下来是由操作系统来进行相关的页面请求并将内存的修改写入到文件当中。我们的应用程序只需要处理内存的数据,这样可以实现非常迅速的I/O操作。

只读模式来说,如果程序试图进行写操作,则会抛出ReadOnlyBufferException异常

读写模式表明,对结果对缓冲区所做的修改将最终广播到文件。但这个修改可能会也可能不会被其他映射了相同文件程序可见。

私有模式来说,对结果缓冲区的修改将不会被广播到文件并且也不会对其他映射了相同文件的程序可见。取而代之的是,它将导致被修改部分缓冲区独自拷贝一份到用户空间。这便是OS的“copy on write”原则。

如果操作系统底层支持的话transferTo、transferFrom也会使用相关的零拷贝技术来实现数据的传输。所以,这里是否使用零拷贝必须依赖于底层的系统实现。

linux 请问串口操作write(fd,buffer,bufflen);连续发送几千次后就无法再向外发送数据【但是可以读数据】

更好掘答有代码,这样分析抓不准,可能原因:

缓冲区问题,不过串口缓冲应该逗毕支持不了几千次

字符串问题,类似申请了内存没释放,或者产生了越界或者乱码之类的

读取正常,说明串口本身没问题,除了判指慧程序本身,那么还可能是接收端处理的问题

还有这种事?

关于linux fd 泄漏的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。


数据运维技术 » 避免被攻击:如何解决Linux文件描述符泄漏问题? (linux fd 泄漏)