当前位置: 首页 > 科技观察

当你在Linux上启动一个进程时会发生什么?

时间:2023-03-21 20:33:57 科技观察

这篇文章是关于fork和exec如何在Unix上工作的。你可能已经知道了,有些人可能还不知道。几年前,当我了解到这一点时,我感到非常敬畏。我们所做的是开始一个过程。我们已经发布了很多关于系统调用的博客,每次启动进程或打开文件时,都是系统调用。所以你可能认为有一个像这样的系统调用:start_process(["ls","-l","my_cool_directory"])这是一个合理的想法,显然这就是它在DOS或Windows中的工作方式。我想说的是,这不是它在Linux上的工作方式。但是,我查看了文档,确实有一个posix_spawn系统调用基本上可以执行此操作,但这超出了本文的范围。Linux上的fork和execposix_spawn是通过两个系统调用实现的,fork和exec(实际上是execve),这两个系统调用是人们常用的。尽管在OSX上人们不鼓励使用posix_spawn和fork和exec,但我们将讨论Linux。Linux中的每个进程都存在于“进程树”中。您可以通过运行pstree命令来查看进程树。树的根是init,进程号是1。每个进程(init除外)都有一个父进程,一个进程可以有多个子进程。所以,假设我想启动一个名为ls的进程来列出一个目录。我只需要启动一个进程ls吗?不。我做的是,创建一个子进程,它是我(me)本身的一个克隆,然后这个子进程的“大脑”被吃掉了,变成了ls。开始是这样的:myparent|-me然后运行fork()生成一个子进程,它是我自己(me)的克隆:myparent|-me|--cloneofme然后我让孩子processrunexec("ls"),变成这样:myparent|-me|--ls当ls命令结束时,我差点又变成了自己:myparent|-me|--ls(zombie)此时ls实际上是一个僵尸进程。这意味着它已经死了,但它仍在等待我,以防我需要检查它的返回值(使用wait系统调用)。一旦我得到它的返回值,我就又回到了自己身边。我的父母|-mefork和exec的代码实现如果你要编写一个shell,这是一个你必须做的练习(这是一个非常有趣和有指导意义的项目。Kamal在Github上有一个很棒的研讨会:https://github。com/kamalmarhubi/shell-workshop)。事实证明,使用C或Python技能,您可以在几个小时内编写一个非常简单的shell,如bash。(至少如果你旁边有懂一点的人,否则就需要更长的时间。)我完成了,真的很酷。这是程序中fork和exec的实现。我写了一段C伪代码。请记住,分叉也可能失败。intpid=fork();//我要做克隆人//“我”是谁?可能是子进程也可能是父进程if(pid==0){//我现在是子进程//"ls"吃了我的脑子变成了完全不同的进程exec(["ls"])}elseif(pid==-1){//天哪,fork失败了,灾难!}else{//我是父进程//继续做个酷帅哥//如果有必要,我可以等子进程结束}上面说的“脑子被吃掉”是什么意思?进程有很多属性:打开文件(包括打开的网络连接)环境变量信号处理程序(当您在程序上运行Ctrl+C时会发生什么?)内存(您的“地址空间”)注册可执行文件(/proc/$pid/exe)cgroupsandnamespaces(与linux容器相关)当前工作目录user运行程序others我还没有想到当你运行execve让另一个程序吃掉你的脑袋时,其实几乎一切都是一样的!你有相同的环境变量、信号处理程序、打开的文件等。***改变的是内存、寄存器和正在运行的程序,这很重要。为什么fork不是那么占用资源(写时复制)您可能会问:“如果我有一个使用2GB内存的进程,那是否意味着每次启动子进程时,都必须复制所有2GB内存一次?听起来资源不少啊!”事实上,Linux为fork()调用实现了写时复制,这对于新进程的2GB内存来说就像“看看旧进程一样!”。然后,当任何进程试图写入内存时,系统实际上会为该进程制作一份内存副本。如果两个进程的内存相同,则不需要复制。为什么你需要知道这么多?你可能会说,好吧,这些细节听起来很棒,但为什么它们如此重要?是否会继承有关信号处理程序或环境变量的详细信息?这实际上如何影响我的日常编程?有可能的!例如,Kamal的博客上有一个有趣的错误。它讨论了Python如何使信号处理程序忽略SIGPIPE。也就是说,如果您从Python运行程序,它默认会忽略SIGPIPE!这意味着程序从Python脚本启动时的行为与从shell启动时的行为不同。在这种情况下,它会产生一个奇怪的问题。因此,您的程序的环境(环境变量、信号处理程序等)可能很重要,它们都是从父进程继承的。了解这一点在调试时很有用。