为什么malloc()被认为是一个库调用而不是系统调用?

6
为什么malloc()被认为是标准的C库函数,而不是系统调用?看起来操作系统负责处理所有内存分配请求。

4
malloc 是一个库函数,可以有各种实现方式,包括一些可能使用系统调用的方式(也可能不使用)。 - Eugene Sh.
@4386427 然后检查一下在一些裸机环境下的实现 :) - Eugene Sh.
1
“库调用”和“系统调用”的区别已经变得有些虚假。在几乎所有操作系统上,您的C代码所做的每个调用都是一个“库”调用,它可能映射到零个、一个或多个实际的系统调用在“库”函数内部。即使是glibc的syscall()本身也是作为一个函数(尽管是用汇编语言实现的)。除非您编写自己的汇编程序直接调用CPU的实际系统调用指令,否则您正在调用一个函数。 - Andrew Henle
4个回答

7
实现将mallocfree作为系统调用是完全可行的,但这种方式很少被使用。
系统调用是对操作系统内核的调用。例如,在POSIX系统(Linux、UNIX等)中,readwrite就是系统调用。当C程序调用read时,它可能调用一个包装器,该包装器执行所有必要的操作以向内核发出请求,然后将结果返回给调用者。
事实证明,进行内存管理的最有效方法是使用低级别的系统调用(参见brksbrk)来扩展当前进程的数据段,然后使用库调用(如mallocfree等)在该段内管理内存。该管理不需要与内核的任何交互;这只是在当前进程内执行的指针操作。如果malloc函数需要比当前可用的内存更多的内存,则会调用像brksbrk这样的系统调用,但许多malloc调用根本不需要与内核进行任何交互。
上述内容与Linux / POSIX / UNIX系统有关。对于Windows等其他操作系统,细节可能有所不同,但整体设计可能类似。
请注意,一些C标准库函数通常直接实现为系统调用。 time就是一个例子(但正如Nick ODell在评论中指出的那样,time调用经常可以在不与内核交互的情况下执行)。

1
这里有一个细节需要指出:time函数并不是一个很好的例子,因为如果启用了vDSO,它可以在没有系统调用的情况下获得:https://unix.stackexchange.com/questions/316074/why-dont-linux-utils-use-a-system-call-to-get-the-current-time 但总的来说,你的帖子是正确的。非常棒! - Nick ODell
4
@NickODell,我法律上有责任指出你拼错了“Pedantic”。 - Keith Thompson
非常好的回答,谢谢。事后看来,在使用malloc时联系操作系统似乎非常不必要。 - Izzo

3
似乎操作系统负责处理所有内存分配请求。
嗯,有点是有点不是。
实际上,这更取决于您特定的系统,而不是C语言本身。
大多数操作系统按一些大小来分配内存。通常称为页面。页面大小可能不同,并且在特定系统上可能支持几个页面大小。4K是许多系统上的典型页面大小,但也可能支持比它大得多的大页面。
但是,最终只有一个实体可以分配内存。操作系统。除非您处于裸金属状态,否则其他代码可以处理它(如果支持)。
malloc()为什么被认为是标准的C库函数而不是系统调用?
简短回答:因为malloc不是OS / system call。句号。
进一步解释一下。一个malloc调用可能导致一个系统调用,但下一个malloc可能不会。
例如:您使用malloc请求100字节。malloc可能决定调用操作系统。操作系统提供了4K。在您的下一个malloc中,您请求500字节。然后,“中间层”可以从前面一个syscall已经提供的trunk中直接提供500个字节。
所以,通过malloc进行内存分配可能不需要任何分配更多内存的系统调用。
这完全取决于您特定的系统。并且C标准不关心这些。
但malloc不是系统调用。malloc在需要时使用其他系统调用。

1
似乎操作系统负责处理所有内存分配请求。
为了性能考虑,每次程序需要内存时向操作系统请求内存并不是一个好主意。有几个原因:
1. 操作系统按页面管理内存。页面通常为 4096 字节长。 (但某些体系结构或操作系统使用更大页面。)操作系统不能将内存分配给进程,以小于页面大小的块。 假设你需要 10 字节来存储一个字符串。分配 4096 字节并仅使用前 10 字节将非常浪费。内存分配器可以向操作系统请求页面,并将该页面分成较小的分配。
2. 系统调用需要上下文切换。相对于在同一程序中调用函数,上下文切换是昂贵的(在 x86 系统上约为 100 纳秒)。再次提醒,最好请求更大的内存块,并将其重复使用以进行许多分配。
为什么 malloc() 被认为是库调用而不是系统调用?
对于一些库调用,比如read(),在库中的实现非常简单:它会调用同名的系统调用。一次对库函数read()的调用会产生一次对read()的系统调用。可以合理地将read()描述为系统调用,因为所有工作都是在内核中完成的。 malloc()的情况就更加复杂了。没有一个叫做malloc()的系统调用,而库调用malloc()实际上会使用sbrk()brk()mmap()等系统调用,具体取决于你的分配大小和你使用的实现方式。很多时候,它根本不会进行系统调用!
有许多不同的选择来实现malloc()。因此,你会看到许多竞争的实现,比如jemalloctcmalloc

0
为什么malloc()被认为是标准的C库函数而不是系统调用?
因为它是C标准库的一部分。
似乎操作系统负责处理所有内存分配请求。
并非如此。操作系统通常为给定进程分配一些内存空间,但之后内存的使用方式取决于该进程。使用标准库来进行诸如内存分配之类的操作可以使您的代码与任何特定操作系统的细节隔离开来,从而使您的代码更具可移植性。给定的malloc实现可能最终会调用系统调用来获取内存,但它是否这样做或者有时候这样做都是实现细节。

公平地说,一些malloc实现中使用的brk/sbrk调用确实是系统调用。 - Eugene Sh.
“@4386427 关于“归根结底,操作系统负责所有内存分配”的回复,这并不总是正确的。尽管公平地说,在这种情况下,可能没有malloc函数的系统。” - ikegami
1
@4386427 对,我的意思是它与“操作系统通常为给定进程分配一些内存空间,但之后如何使用内存取决于该进程”这一说法不符。brk/sbrk正在改变初始分配。 - Eugene Sh.
从C程序员的角度来看,malloc的实现方式并不重要。标准库的一个重要特性是它隔离了程序员与操作系统特定的细节。你提到brksbrk这一事实表明你有一个非常Unix中心的观点,这并没有错,但是还有很多其他没有这些调用的系统存在。即使我在Arduino上工作,malloc()仍然可以工作,尽管根本没有真正的操作系统。 - Caleb
@Caleb 我知道你所说的一切并且同意,你可以查看我的评论来使自己信服 :) 我只是指出你的回答包含了一些不总是正确的范畴性声明。 - Eugene Sh.
1
@EugeneSh。我添加了更多的解释,这可能有助于满足您的担忧。OP的问题非常广泛,因此即使特定细节可能因系统而异,但在我看来,一个大体正确的答案似乎是合适的。 - Caleb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接