当我在 shell 中执行文件时到底会发生什么?

当我在 shell 中执行文件时到底会发生什么?

所以,我以为我对此有很好的理解,但刚刚进行了一个测试(为了回应我不同意某人的对话),发现我的理解是有缺陷的......

尽可能详细当我在 shell 中执行文件时到底会发生什么?我的意思是,如果我./somefile some arguments在 shell 中输入: 并按回车键(并且somefile存在于 cwd 中,并且我具有读取+执行权限somefile),那么幕后会发生什么?

想法答案是:

  1. shell 对 进行系统调用exec,将路径传递给somefile
  2. 内核检查somefile并查看神奇的数字文件以确定它是否是处理器可以处理的格式
  3. 如果幻数表明该文件采用处理器可以执行的格式,则
    1. 创建一个新进程(在进程表中有一个条目)
    2. somefile被读取/映射到内存。创建堆栈,执行跳转到 代码的入口点somefile,并ARGV初始化为参数数组 (a char**, ["some","arguments"])
  4. 如果幻数是舍邦然后exec()如上所述生成一个新进程,但使用的可执行文件是 shebang 引用的解释器(例如/bin/bash/bin/perl)并somefile传递给STDIN
  5. 如果文件没有有效的幻数,则会出现“无效文件(坏幻数):Exec 格式错误”之类的错误

然而,有人告诉我,如果文件是纯文本,那么 shell 会尝试执行命令(就像我输入了bash somefile)。我本来不相信这一点,但我只是尝试了一下,结果是正确的。所以我显然对这里实际发生的事情有一些误解,并且想了解其中的机制。

当我在 shell 中执行文件时到底会发生什么? (尽可能详细地合理......)

答案1

关于“程序如何在 Linux 上运行”的明确答案是以下两篇文章:绿网网标题,令人惊讶的是,程序如何运行程序如何运行:ELF 二进制文件。第一篇文章简要介绍了脚本。 (严格来说,最终的答案在源代码中,但这些文章更容易阅读并提供源代码的链接。)

一些实验表明您几乎做对了,并且包含简单命令列表的文件的执行(没有 shebang)需要由 shell 处理。这执行(2)联机帮助页包含测试程序 execve 的源代码;我们将用它来看看没有 shell 会发生什么。首先,编写一个测试脚本,testscr1包含

#!/bin/sh

pstree

和另一个,testscr2,仅包含

pstree

使它们都可执行,并验证它们是否都从 shell 运行:

chmod u+x testscr[12]
./testscr1 | less
./testscr2 | less

现在再试一次,使用execve(假设您在当前目录中构建它):

./execve ./testscr1
./execve ./testscr2

testscr1仍然运行,但testscr2产生

execve: Exec format error

这表明 shell 的处理方式testscr2不同。虽然它本身不处理脚本,但它仍然用来/bin/sh这样做;这可以通过管道testscr2来验证less

./testscr2 | less -ppstree

在我的系统上,我得到

    |-gnome-terminal--+-4*[zsh]
    |                 |-zsh-+-less
    |                 |     `-sh---pstree

正如您所看到的,我使用的 shellzsh启动了less,第二个 shell 是普通的shdash在我的系统上),用于运行脚本,该脚本运行了pstree。这是由inzsh处理的zexecveSrc/exec.c:shell 用于execve(2)尝试运行命令,如果失败,它会读取文件以查看它是否有 shebang,并相应地处理它(内核也会这样做),如果失败,它会尝试运行文件sh,只要它没有从文件中读取任何零字节:

        for (t0 = 0; t0 != ct; t0++)
            if (!execvebuf[t0])
                break;
        if (t0 == ct) {
            argv[-1] = "sh";
            winch_unblock();
            execve("/bin/sh", argv - 1, newenvp);
        }

bash具有相同的行为,实施于execute_cmd.c带有有用的评论(正如所指出的塔列津):

执行一个简单的命令,希望该命令在某个磁盘文件中定义。

  1. fork ()
  2. 连接管道
  3. 查找命令
  4. 进行重定向
  5. execve ()
  6. 如果execve失败,请查看文件是否设置了可执行模式。如果是,并且它不是目录,则将其内容作为 shell 脚本执行。

POSIX 定义了一组函数,称为功能exec(3),它execve(2)也包装并提供此功能;看穆鲁的答案以了解详细信息。在Linux上,至少这些函数是由C库实现的,而不是由内核实现的。

答案2

在某种程度上,这取决于exec所使用的特定家庭功能。execve, 作为斯蒂芬·基特已详细显示,仅运行正确的二进制格式的文件或以正确的 shebang 开头的脚本。

然而execlpexecvp更进一步:如果 shebang 不正确,则/bin/sh在 Linux 上执行该文件。从man 3 exec:

Special semantics for execlp() and execvp()
   The execlp(), execvp(), and execvpe() functions duplicate the actions
   of the shell in searching for an executable file if the specified
   filename does not contain a slash (/) character.

   If the header of a file isn't recognized (the attempted execve(2)
   failed with the error ENOEXEC), these functions will execute the
   shell (/bin/sh) with the path of the file as its first argument.  (If
   this attempt fails, no further searching is done.)

这在一定程度上得到了支持POSIX(强调我的):

标准开发人员指出的一个潜在的混乱来源是进程映像文件的内容如何影响 exec 系列函数的行为。以下是所采取行动的描述:

  1. 如果进程映像文件是该系统的有效可执行文件(采用可执行且有效且具有适当权限的格式),则系统执行该文件。

  2. 如果进程映像文件具有适当的权限并且采用可执行但对该系统无效的格式(例如其他体系结构的可识别二进制文件),则这是一个错误,并且 errno 设置为 [EINVAL](请参阅后面的原理)上 [EINVAL])。

  3. 如果进程映像文件具有适当的权限但未被识别:

    1. 如果这是对 execlp() 或 execvp() 的调用,则它们会调用命令解释器,并假设进程映像文件是 shell 脚本。

    2. 如果这不是对 execlp() 或 execvp() 的调用,则会发生错误,并且 errno 将设置为 [ENOEXEC]。

这没有指定如何获取命令解释器,因此,但没有指定必须给出错误。因此,我猜想 Linux 开发人员允许运行此类文件/bin/sh(或者这已经是一种常见的做法,他们只是效仿)。

FWIW,FreeBSD 联机帮助页exec(3)还提到了类似的行为:

 Some of these functions have special semantics.

 The functions execlp(), execvp(), and execvP() will duplicate the actions
 of the shell in searching for an executable file if the specified file
 name does not contain a slash ``/'' character. 
 If the header of a file is not recognized (the attempted execve()
 returned ENOEXEC), these functions will execute the shell with the path
 of the file as its first argument.  (If this attempt fails, no further
 searching is done.)

然而,AFAICT 没有通用 shell 使用execlpexecvp直接使用,大概是为了更好地控制环境。它们都使用 实现相同的逻辑execve

答案3

bash这可能是对 Stephen Kitt 答案的补充,作为文件中来源的评论execute_cmd.c

执行一个简单的命令,希望该命令在某个磁盘文件中定义。

1. fork ()
2. connect pipes
3. look up the command
4. do redirections
5. execve ()
6. If the execve failed, see if the file has executable mode set.  

如果是,并且它不是目录,则将其内容作为 shell 脚本执行。

答案4

它作为 shell 脚本执行,它是不是来源(例如,在执行文件中设置的变量不会影响外部)。可能是遥远的过去的遗迹,当时有壳和可执行格式。不是可执行文件,它必须是 shell 脚本。

相关内容