수정 이력
25.06.02 삽화 수정 및 표준 에러 처리 분기 설명 수정
목차
들어가기 전에
nohup 뜻
알고 가면 좋을 것
- dup2(odlfd, newfd)
- tty
- /dev/null
isatty
- ignoring_input
- redirecting_stdout
- stdout_is_closed
- redirecting_stderr
마치면서
Reference
들어가기 전에
이 글은 nohup 사용법에 대해 다루지 않습니다.
다음 내용들을 알고 있다 가정 하에 글을 썼습니다.
- 표준 입출력, 에러의 파일 디스크립터 값
- C의 파일 입출력
- 파일 디스크립터가 무엇인지
여기 코드를 기준으로 작성했습니다.
https://github.com/coreutils/coreutils/blob/master/src/nohup.c
coreutils/src/nohup.c at master · coreutils/coreutils
upstream mirror. Contribute to coreutils/coreutils development by creating an account on GitHub.
github.com
nohup 뜻
노헙
no hangup 이란 뜻으로 HUP(hangup) 신호를 무시하도록 만드는 POSIX 명령어
HUP은 터미널이 의존 프로세스들에게 로그아웃을 알리는 방식이다. - 위키 백과 -
코드를 보면 SIGHUP 시그널에 대해 SIG_IGN 처리됐는데 중단 시그널을 무시하란 뜻이다.
알고 가면 좋을 것
dup2(odlfd, newfd)
dup()와 유사한 수행을 한다.
하지만, 아직 사용되지 않는 가장 낮은 숫자의 파일 디스크립터를 사용하는 게 아니라
2번째 인자인 newfd에 명시된 파일 디스크립터를 사용한다.
다르게 말하면, oldfd 가 참조하고 있는 open file description 을 똑같이 참조하도록 newfd를 조절한다.

tty
tty는 Unix 및 Linux 시스템에서 "teletypewriter"의 약자로, 시스템에서 터미널(콘솔) 을 의미합니다.
현대의 tty는 실제 물리적인 전신 타자기(teleprinter)는 아니고,
그 개념에서 유래한 입출력 인터페이스(가상 혹은 물리적) 를 의미합니다.
Further investigation would lead you to the discovery that file descriptors 0, 1 and 2 (aka STDIN_FILENO, STDOUT_FILENO and STDERR_FILENO) are by convention set up to point to your terminal when your program is running from a terminal.
더 자세히 조사해 보면, 프로그램이 터미널에서 실행될 때 파일 설명자
0, 1, 2(STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO라고도 함)가
관례적으로 터미널을 가리키도록 설정되어 있다는 사실을 발견할 수 있습니다.
내가 입력한 것들은 /dev/tty, /dev/pts/N 같은 터미널 장치로 전달되고
isatty는 매개변수로 받은 파일 디스크립터가 터미널인지 확인한다.
레퍼런스 체크를 바로바로 못해서 Chatgpt 답변인지 스오플 답변인지 모르겠다.
/dev/null
리눅스/유닉스에 존재하는 특수 장치 파일. 어떤 데이터를 쓰더라도 버리고, 읽으면 EOF를 반환한다.
쉽게 말하면 짬통이다. (내가 비울 필요도 없다!)
isatty
장치가 터미널인지 확인하는 시스템 콜
파일 디스크립터가 터미널을 참조하는 오픈된 디스크립터이면 1을 반환한다.
아래 4가지 값을 보고, 표준 입출력과 여기에 참조되는 파일을 정의한 뒤 nohup 프로그램을 실행한다.
다음과 같이 isatty로 표준 입출력, 에러의 상태를 확인한다.
ignoring_input = isatty (STDIN_FILENO); // 터미널로 받는 입력은 무시
redirecting_stdout = isatty (STDOUT_FILENO); // 파일로 출력하면 0, 터미널이면 1
stdout_is_closed = (!redirecting_stdout && errno == EBADF); // 출력을 지정한 파일이 유효한지
redirecting_stderr = isatty (STDERR_FILENO); // 파일로 출력하면 0, 터미널이면 1
이 과정이 끝나면 execvp 시스템 콜을 사용해 우리가 매개변수로 전달한 프로그램을 실행시킨다.
ignoring_input
터미널을 통한 표준 입력을 무시한다.

만약, nohup test.sh &처럼 명령어 끝에 &를 입력해 백그라운드로 실행하면 0이 된다.
하지만 이를 생략할 경우 1이 되는데, 다음 분기를 통해 /dev/null로 입력을 연결한다.
/* If standard input is a tty, replace it with /dev/null if possible.
Note that it is deliberately opened for *writing*,
to ensure any read evokes an error. */
if (ignoring_input)
{
if (fd_reopen (STDIN_FILENO, "/dev/null", O_WRONLY, 0) < 0) // < 이부분
error (exit_internal_failure, errno,
_("failed to render standard input unusable"));
if (!redirecting_stdout && !redirecting_stderr)
error (0, 0, _("ignoring input"));
}
요약하면 ignoring_input 값이 0이든, 1이든 입력은 무시한다.
애초에 nohup 설계 의도가 HUP 신호를 무시해야 하기 때문에 터미널과 상호작용하지 않아야 한다.
만약, 터미널과 상호작용하더라도, 세션 종료 등의 이유로 stdin이 닫히거나 차단돼 프로그램이 멈출 수 있기 때문에
강제로 /dev/null로 연결해 표준 입력을 막는다.
redirecting_stdout
아무튼 생성된 표준 출력을 파일로 쓰도록 하기 위해 만든 변수

redirecting_stdout 을 통해 stdout을 처리하는 코드다.
/* If standard output is a tty, redirect it (appending) to a file.
First try nohup.out, then $HOME/nohup.out. If standard error is
a tty and standard output is closed, open nohup.out or
$HOME/nohup.out without redirecting anything. */
if (redirecting_stdout || (redirecting_stderr && stdout_is_closed))
{
char *in_home = nullptr;
char const *file = "nohup.out";
int flags = O_CREAT | O_WRONLY | O_APPEND;
mode_t mode = S_IRUSR | S_IWUSR;
mode_t umask_value = umask (~mode);
out_fd = (redirecting_stdout
? fd_reopen (STDOUT_FILENO, file, flags, mode)
: open (file, flags, mode));
// 생략
redirecting_stdout = 1
nohup test.sh 같이 표준 출력을 터미널로 하면 redirecting_stdout 값은 1이 되고 nohup.out을 생성하고
표준 출력이 nohup.out의 파일 디스크립터를 참조하게 한다.
redirecting_stdout = 0
nohup test.sh > test.out 같이 표준 출력을 파일로 지정하면 터미널 출력이 아니기 때문에 특별한 처리를 하진 않는다.
하지만, 표준 에러를 터미널로 처리하고,(redirecting_stderr = 1)
표준 출력이 참조하는 파일의 파일 디스크립터가 유효하지 않으면(stdout_is_closed = 1)
nohup.out 파일을 만든다.
요약하면 표준 출력이 터미널을 참조하거나, 출력할 파일이 잘못되면 nohup.out에 출력한다는(쓴다는) 것이다.
stdout_is_closed
표준 출력이 파일로 연결됐지만
파일이 유효하지 않은 파일 디스크립터인지 확인하는 플래그

이 상태 값은 직전에 다뤘던 표준 출력을 다루는 redirecting_stdout 조건문에서 2번째 조건에 쓰이며
nohup.out을 만들게 한다.
redirecting_stderr
터미널을 통한 표준 에러 stderr를 리다이렉트
nohup ./test.sh 2> error.log 같이 stderr를 파일로 리다이렉트 하면
redirecting_stderr 값은 0이지만, 터미널로 출력하면 1이 된다.
redirecting_stderr를 통해 표준 에러를 처리하는 코드다.
/* If standard error is a tty, redirect it. */
if (redirecting_stderr)
{
/* Save a copy of stderr before redirecting, so we can use the original
if execve fails. It's no big deal if this dup fails. It might
not change anything, and at worst, it'll lead to suppression of
the post-failed-execve diagnostic. */
// 1) stderr 복제본 저장
saved_stderr_fd = fcntl (STDERR_FILENO, F_DUPFD_CLOEXEC,
STDERR_FILENO + 1);
// 2) 출력 설명 메시지
if (!redirecting_stdout)
error (0, 0,
_(ignoring_input
? N_("ignoring input and redirecting stderr to stdout")
: N_("redirecting stderr to stdout")));
// 3) 에러 메시지 리다이렉션
if (dup2 (out_fd, STDERR_FILENO) < 0)
error (exit_internal_failure, errno,
_("failed to redirect standard error"));
// 4) 직전 redirecting_stdout 분기에서 만든 nohup.out fd 회수
if (stdout_is_closed)
close (out_fd);
}
1) stderr 복제본 저장
// 1) 표준 에러 복제본 저장
saved_stderr_fd = fcntl (STDERR_FILENO, F_DUPFD_CLOEXEC,
STDERR_FILENO + 1);
STDERR_FILENO(=2)보다 큰 파일 디스크립터 중 사용 가능한 것을 찾아 복사
F_DUPFD_CLOEXEC는 execve() 호출 시 자동으로 close 되지 않도록 한다.
즉, execve()계통 시스템 콜이 실패하면 원래 stderr로 에러 메시지를 다시 출력할 수 있도록 한다.
2) 출력 설명 메시지
// 2) 출력 설명 메시지
if (!redirecting_stdout)
error (0, 0,
_(ignoring_input
? N_("ignoring input and redirecting stderr to stdout")
: N_("redirecting stderr to stdout")));
error 함수를 호출하지만, 그냥 상황 설명을 출력한다.
눈에 잘 띄게 하려고 error로 설정한 것 같음
3) 에러 메시지 리다이렉션
// 3) 에러 메시지 리다이렉션
if (dup2 (out_fd, STDERR_FILENO) < 0)
error (exit_internal_failure, errno,
_("failed to redirect standard error"));
표준 에러를 redirecting_stdout 부분에서 지정한 nohup.out으로 쓰게 한다.
여기서, dup이 아닌, dup2를 쓴 이유는
dup 시스템 콜을 쓰면 미사용 파일 디스크립터에 stderr를 할당한다.
이렇게 되면 표준 출력이 nohup.out이 아닌 다른 곳으로 출력되기 때문에
dup2를 사용해 stderr 파일디스크립터(=2)가 nohup.out의 파일 디스크립터를 참조하도록 조정한다.
이렇게 되면 표준 출력, 에러가 nohup.out의 파일 디스크립터를 참조한다.
4) 표준 출력 파일이 잘못된 경우, 파일 디스크립터 회수
// 4) 직전 redirecting_stdout 분기에서 만든 nohup.out fd 회수
if (stdout_is_closed)
close (out_fd);
만약, 표준 출력이 참조하는 파일의 파일 디스크립터가 유효하지 않다면 표준 출력이 닫혔다는 뜻이 된다.
이러면 nohup은 표준 출력을 처리하지 않게 된다.
따라서 의미 없게 된 표준 출력의 파일 디스크립터를 닫아 자원을 아낀다.
마치면서
당연하게 써왔던 거지만 막상 코드 열어서 분석하려니 알아야 할 게 많았다.
이걸 분석했다고 nohup을 더 잘 쓸 수 있단 생각은 들지 않았지만
다른 기능을 분석하게 되면 이때의 배경 지식이 이해에 큰 도움이 될 것이라 생각한다.
Reference
https://github.com/coreutils/coreutils/blob/master/src/nohup.c
https://ko.wikipedia.org/wiki/Nohup
https://stackoverflow.com/questions/36258224/what-is-isatty-in-c-for
https://stackoverflow.com/questions/56419346/how-does-the-libc-function-isatty-work
https://www.man7.org/linux/man-pages/man3/isatty.3.html
'개발' 카테고리의 다른 글
그림으로 배우는 리눅스 구조 4주차 (1) | 2025.04.10 |
---|---|
그림으로 배우는 리눅스 구조 3주차 (0) | 2025.04.10 |
그림으로 배우는 리눅스 구조 2주차 (0) | 2025.03.29 |
그림으로 배우는 리눅스 구조 1주차 (0) | 2025.03.16 |
[JPA] FindAll이 같은 값만 나와요 (2) | 2025.02.15 |