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

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

我正在慢慢地创建一个 Linux 工作原理的心理模型,并且我尝试尽可能简化我的 Linux 工作原理模型。我们假设内核启动并初始化了它要运行的唯一软件:终端。我们假设该终端能够显示在屏幕上并呈现一些文本,当然还能够从键盘获取输入。我们还假设我输入了可执行文件的名称,并且它知道它在内存中的位置。现在,终端如何运行这个程序?在我的思维模型中,我想到了以下几点:

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

答案1

你的理解还是比较准确的。 shell 使用clone()系统调用来创建新进程。联机帮助页描述了它与以下内容的区别fork()

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

然后,它使用execve()系统调用将当前子进程映像替换为新的进程映像。该系统调用使进程运行程序的代码。

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

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

这就是当程序写入标准输出时文本会显示到终端的原因。您可以使用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

相关内容