나는 Linux가 작동하는 방식에 대한 정신적 모델을 천천히 만들고 있으며 Linux가 작동하는 방식에 대한 모델을 최대한 단순화하려고 노력하고 있습니다. 커널이 부팅되고 실행할 유일한 소프트웨어인 터미널을 초기화한다고 가정합니다. 우리는 터미널이 화면에 표시하고 일부 텍스트를 렌더링할 수 있으며 물론 키보드에서 입력을 받을 수 있다고 가정합니다. 또한 실행 파일의 이름을 입력했고 실행 파일이 메모리 내 어디에 있는지 알고 있다고 가정해 보겠습니다. 이제 터미널은 이 프로그램을 어떻게 실행합니까? 내 정신 모델에서 나는 다음을 생각해 냈습니다.
터미널은 프로그램이므로 시스템 호출을 할 수 있습니다. 그래서 fork() 시스템 호출을 사용하고 커널에 새로운 프로세스를 생성합니다. 그런 다음 어떻게든 해당 프로세스가 내 프로그램 코드를 실행하게 만듭니다. 이제 프로그램이 실행되는 동안 어떻게 printf()가 터미널에 실시간으로 텍스트를 표시할 수 있습니까?
답변1
당신의 이해는 매우 정확합니다. 쉘은 clone()
시스템 호출을 사용하여 새 프로세스를 생성합니다. 맨페이지에서는 이것이 다음과 어떻게 다른지 설명합니다 fork()
.
fork(2)와 달리 clone()을 사용하면 하위 프로세스가 호출 프로세스와 메모리 공간, 파일 설명자 테이블 및 신호 처리기 테이블과 같은 실행 컨텍스트의 일부를 공유할 수 있습니다. (이 매뉴얼 페이지에서 "호출 프로세스"는 일반적으로 "상위 프로세스"에 해당합니다.
그런 다음 execve()
시스템 호출을 사용하여 현재 하위 프로세스 이미지를 새 프로세스 이미지로 바꿉니다. 이 시스템 호출은 프로세스가 프로그램의 코드를 실행하도록 합니다.
프로세스가 분기되면 상위 프로세스의 파일 설명자가 복사됩니다. fork(2)
매뉴얼 페이지 에서 :
하위 프로세스는 상위 프로세스의 열린 파일 설명자 세트 사본을 상속합니다.
자식의 각 파일 설명자는 부모의 해당 파일 설명자와 동일한 열린 파일 설명(open(2) 참조)을 참조합니다. 이는 두 설명자가 열린 파일 상태 플래그, 현재 파일 오프셋 및 신호 구동 I/O 속성을 공유한다는 것을 의미합니다(fcntl(2)의 F_SETOWN 및 F_SETSIG 설명 참조).
이것이 프로그램이 표준 출력에 쓸 때 터미널에 텍스트가 표시되는 이유입니다. strace
Linux의 프로그램을 사용하여 이 프로세스가 진행되는 것을 볼 수 있습니다 . 다음은 strace
Linux의 bash 프로세스에서 실행되고 /bin/echo foo
셸에서 실행되는 주요 발췌 내용입니다.
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