为什么类Unix系统在调用函数而不是动态库时会执行一个新进程?与调用动态库相比,创建新进程在性能方面代价高昂。
答案1
类 Unix 系统不会“通过执行新进程来调用函数”。他们(现在)拥有像几乎所有其他相对现代的操作系统一样的共享库。
另一方面,Shell 确实执行其他进程来完成某些任务。但不是所有的。它们具有内置函数,直接在 shell 中(或通过共享库)实现最常见和简单的任务(echo
例如由许多 shell 实现为内置函数)。
(顺便说一句,Windows cmd
shell 在这方面与 Unix shell 没有什么不同。)
在现代类 Unix 系统中创建进程肯定比进行进程内函数调用更昂贵,但幅度也不是那么大。内核针对快速分叉进行了优化,使用以下技术写时复制用于地址空间管理以加速进程的“克隆”,并共享动态库的文本(代码)页。
如果您计算机上可从 shell 脚本调用的每个可执行文件都作为共享库实现,则:
- 启动你的 shell 需要一个很多时间(和内存)只是为了预先加载所有这些东西(即使使用缓存,动态链接器也有不平凡的工作要做,并且库有数据部分,而不仅仅是文本部分 - 我们正在谈论数百甚至数千个图书馆在这里)
- 您必须按需加载每个必要的库 - 可能比启动进程快一点,但这里的优势非常薄弱。而且共享库的数据部分变得非常难以管理(shell 的全局状态现在取决于其地址空间中加载的许多不相关代码和数据的状态)。
因此,对于典型用法,您可能不会获得太多收益,并且稳定性/复杂性变得更加重要。
另一件事是,单独的进程模型非常有效地隔离每个任务(假设虚拟内存管理和保护)。在“一切都是库”模型中,任何实用程序库中的错误都可能污染(即损坏)整个 shell。某些随机实用程序中的错误可能会完全终止您的 shell 进程。
多进程模型的情况并非如此,shell 可以避免其运行的程序中出现此类错误。
还有一点:低耦合。当我/usr/bin
现在查看目录中的内容时,我有:
- ELF 64 位可执行文件,
- ELF 32 位可执行文件,
- Perl 脚本,
- Shell 脚本(其中一些运行 Java 程序),
- Ruby 脚本和
- Python脚本
...而且我可能没有最奇特的系统。您根本无法在同一过程中混合前两种类型。为所有其他人提供一个正在处理的口译员根本不切实际。
即使您只查看“本机二进制”文件格式,“实用程序”之间的接口是简单的流和退出代码,也会使事情变得更简单。
对实用程序的唯一要求是实现操作系统的 ABI 和系统调用。不同实用程序之间(几乎)没有依赖性。对于进程内接口来说,这要么极其困难,要么根本不可能,除非你强加“所有内容都必须使用编译器 Y 的版本 X 进行编译,并具有这样那样的标志/设置”之类的规定。
对于某些事情,进程内调用确实对性能有很大影响,并且这些事情通常已经由 shell 作为内置函数完成。对于其余部分,单独的流程模型非常有效,并且其灵活性是一个很大的优势。