终端如何运行程序并显示其输出?

Emi*_*ito 5 linux command-line shell kernel

我正在慢慢地创建一个关于 linux 工作原理的心智模型,并且我已经尽量简化了我的 linux 工作模型。让我们假设内核启动并初始化它将运行的唯一软件:终端。让我们假设这个终端具有出现在屏幕上并呈现一些文本的能力,当然还有从键盘获取输入的能力。我们还假设我输入了一个可执行文件的名称,它知道它在内存中的位置。现在,终端如何运行这个程序?在我的心智模型中,我认为:

终端是一个程序,这意味着它可以进行系统调用。所以它使用 fork() 系统调用并在内核中创建一个新进程。然后,它以某种方式使这个进程运行我的程序代码。现在, printf() 如何在程序运行时在我的终端上实时显示文本?

jor*_*anm 6

你的理解相当准确。shell 使用clone()系统调用来创建一个新进程。手册页描述了它与fork()以下内容的区别:

与 fork(2) 不同,clone() 允许子进程与调用进程共享其执行上下文的一部分,例如内存空间、文件描述符表和信号处理程序表。(请注意,在本手册页上,“调用进程”通常对应于“父进程”。

然后它使用一个execve()系统调用来用一个新的进程映像替换当前的子进程映像。这个系统调用使进程运行你的程序代码。

当进程派生时,父进程的文件描述符被复制。从fork(2)手册页:

子进程继承父进程打开的文件描述符集的副本。
子级中的每个文件描述符都引用与父级中相应文件描述符相同的打开文件描述(请参阅 open(2))。这意味着这两个描述符共享打开文件状态标志、当前文件偏移量和信号驱动的 I/O 属性(参见 fcntl(2) 中 F_SETOWN 和 F_SETSIG 的描述)。

这就是为什么当程序写入 stdout 时会在终端上显示文本的原因。您可以使用straceLinux 中的程序看到这个过程。以下是strace在 Linux 中的 bash 进程上运行并/bin/echo foo在 shell 中执行的主要摘录。

21:32:20 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f3f419f19d0) = 32036
Process 32036 attached
[pid 32017] 21:32:20 wait4(-1,  <unfinished ...>
[pid 32036] 21:32:20 execve("/bin/echo", ["/bin/echo", "foo"], ["XDG_VTNR=8", "KDE_MULTIHEAD=false", "XDG_SESSION_ID=5512", "SSH_AGENT_PID=30259", "DM_CONTROL=/var/run/xdmctl", "TERM=xterm", "SHELL=/bin/bash", "XDM_MANAGED=method=classic", "XDG_SESSION_COOKIE=5c78dafb330601d94d7556bb52a6a2a6-1450467466.154128-547622992", "HISTSIZE=50000", "KONSOLE_DBUS_SERVICE=:1.160", "GTK2_RC_FILES=/etc/gtk-2.0/gtkrc:/home/jordan/.gtkrc-2.0:/home/jordan/.kde/share/config/gtkrc-2.0", "KONSOLE_PROFILE_NAME=Shell", "GTK_RC_FILES=/etc/gtk/gtkrc:/home/jordan/.gtkrc:/home/jordan/.kde/share/config/gtkrc", "GS_LIB=/home/jordan/.fonts", "WINDOWID=92274714", "SHELL_SESSION_ID=5b72a0038b0c4000a9299cae82f340a2", "KDE_FULL_SESSION=true", "USER=jordan", "SSH_AUTH_SOCK=/tmp/ssh-JEjo6RVmNhvR/agent.30205", "SESSION_MANAGER=local/tesla:@/tmp/.ICE-unix/30329,unix/tesla:/tmp/.ICE-unix/30329", "PATH=/home/jordan/.gem/ruby/1.9.1/bin:/home/jordan/.gem/ruby/1.9.1/bin:/home/jordan/bin:/home/jordan/local/packer:/home/jordan/local/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/sbin:/usr/local/sbin:/usr/sbin:/home/jordan/.rvm/bin:/home/jordan/prog/go/bin:/home/jordan/.rvm/bin:/home/jordan/prog/go/bin", "DESKTOP_SESSION=kde-plasma", "PWD=/home/jordan/games", "WORKING=/home/jordan/prog/greenspan", "KONSOLE_DBUS_WINDOW=/Windows/1", "EDITOR=emacs -nw", "LANG=en_US.UTF-8", "KDE_SESSION_UID=1000", "PS1=\\[\\033[01;32m\\]\\u@\\h\\[\\033[01;34m\\] \\w\\[\\033[1;31m\\]$(__git_ps1)\\[\\033[01;34m\\] \\$\\[\\033[00m\\] ", "KONSOLE_DBUS_SESSION=/Sessions/1", "SHLVL=2", "XDG_SEAT=seat0", "COLORFGBG=15;0", "HOME=/home/jordan", "LANGUAGE=", "KDE_SESSION_VERSION=4", "GOROOT=/home/jordan/local/go", "XCURSOR_THEME=oxy-zion", "LOGNAME=jordan", "DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-onouV6Cc66,guid=bcdceeabe7aa00a28d55899f5674608a", "XDG_DATA_DIRS=/usr/share:/usr/share:/usr/local/share", "GOPATH=/home/jordan/prog/go", "PROMPT_COMMAND=history -a", "WINDOWPATH=8", "DISPLAY=:0", "XDG_RUNTIME_DIR=/run/user/1000", "PROFILEHOME=", "QT_PLUGIN_PATH=/home/jordan/.kde/lib/kde4/plugins/:/usr/lib/kde4/plugins/", "XDG_CURRENT_DESKTOP=KDE", "HISTTIMEFORMAT=%F %T: ", "_=/bin/echo"]) = 0
[pid 32036] 21:32:20 write(1, "foo\n", 4) = 4
[pid 32036] 21:32:20 exit_group(0)      = ?
[pid 32036] 21:32:20 +++ exited with 0 +++
21:32:20 <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WSTOPPED|WCONTINUED, NULL) = 32036
Run Code Online (Sandbox Code Playgroud)