본문 바로가기
프로그래밍/UNIX 고급 프로그래밍

제9장 프로세스 관계

by Ohdumak 2019. 12. 2.

9.1 소개

모든 프로세스에는 부모프로세스가 있다. 자식이 종료되면 그 사실이 부모에게 통지되며, 부모는 자식의 종료 상태를 회수할 수 있다.

 이번 장에서는 프로세스 그룹과 POSIX.1에서 도입된 세션(session)을 소개한다.

9.2 터미널 로그인

터미널을 이용해서 유닉스 시스템에 로그인하는 데 쓰이는 절차를 설명한다.

BSD터미널 로그인

  1. 시스템 관리자는 터미널 장치당 한 줄씩으로 이루어진 파일을 생성한다. (/etc/ttys)
  2. 파일의 각 줄에는 장치 이름과 getty 프로그램에 전달할 기타 매개변수들이 지정되어 있다.
  3. 시스템이 시동(booting)될 때 커널은 프로세스 ID가 1인 init 프로세스를 생성
  4. init 프로세스는 /etc/ttys 파일을 읽어서 로그인을 허용하는 각각의 터미널 장치마다 fork-exec의 조합으로 getty 프로그램을 실행
  5. login: 같은 프롬프트를 출력하고 사용자가 로그인 이름을 입력하길 기다린다.
  6. 로그인 이름을 입력하면 getty의 임무 완료
  7. login 프로그램을 실행
  8. login 프로그램은 사용자 항목을 조회한 후에는 getpass를 호출해서 Password: 프롬프트를 표시하고 사용자의 패스워드를 입력 받는다.
  9. crypt를 호출해서 입력된 패스워드를 암호화 후 결과를 그림자 패스워드 파일 항목의 pw_passwd 필드와 비교한다.
  10. 둘이 일치하지 않아 로그인 실패시 여러번 반복되면 login은 인수 1로 exit 호출
  11. 프로세스 종료가 부모 프로세스(init)에 통지되면, fork-exec 조합으로 getty를 다시 실행해서 새로운 터미널을 띄운다.

로그인이 성공하면 login은 다음 일을 수행한다.

  • 현재 작업 디렉터리를 사용자의 홈 디렉터리로 변경(chdir)
  • 사용자가 터미널 장치를 소유하도록 장치의 소유권을 변경(chown)
  • 사용자가 터미널을 읽고 쓸 수 있도록 터미널 장치에 대한 접근 권한을 변경
  • setgid와 initgroups를 호출해서 사용자의 그룹 ID들을 변경
  • login이 가진 모든 정보를 환경에 초기화
    • 홈 디렉터리(HOME), 셸(SHELL), 사용자 이름(USER, LOGNAME), 기본 경로(PATH)가 포함
  • 사용자의 사용자 ID로 전환한 후(setuid를 이용) 사용자의 로그인 셸을 다음과 같은 형태로 띄운다.
    • execl("/bin/sh", "-sh", (char *)0);

로그인 셸은 시동 파일들(본 셸과 콘 셸의 경우 .profile, GNU 본어게인 셸의 경우 .bash_profile이나 .bash_login, .profile, C 셸의 경우 .cshrc과 .login)을 읽고 환경변수를 추가한다.

 

Mac OS X의 터미널 로그인

Mac OS X는 부분적으로 FreeBSD에 기초한 것이기 때문에 BSD의 로그인 과정과 본질적으로 동일하다.

단, Mac OS X만의 특징도 있다.

  • init이 하는 일을 launchd라는 프로세스가 실행한다.
  • 처음부터 그래픽 기반 로그인 화면이 나타난다.

Linux의 터미널 로그인

Linux 로그인 절차는 BSD의 절차와 아주 비슷하다. BSD 로그인 절차와 Linux 로그인 절차의 주된 차이는 터미널 구성을 지정하는 방식에 있다.

 

Solaris 터미널 로그인

두 종류의 터미널 로그인을 지원한다. BSD 계열의 getty 스타일, SVR4에서 도입된 ttymon 스타일이다.

 ttymon 명령은 SAF(Service Access Facility)라고하고, 목표는 시스템에 대한 접근을 제공하는 서비스들을 좀 더 일관된 방식으로 관리할 수 있는 방법을 제공한다.

 

 

9.3 네트워크 로그인

직렬 터미널을 통해 시스템에 로그인하는 것과 네트워크를 통해 시스템에 로그인하는 것의 주된 차이점은, 터미널과 컴퓨터 사이의 연결이 점대점(point-to-point)이 아니라는 것이다. 네트워크 로그인의 경우 login은 그냥 사용 가능한 하나의 서비스일 뿐이다. FTP나 SMTP같은 다른 네트워크 서비스와 다를 바 없다.

 하나의 프로세스로 모든 가능한 로그인을 기다리지 않고, 네트워크 연결 요청이 도달하길 기다린다.

 

BSD 네트워크 로그인

BSD에서는 하나의 프로세스가 대부분의 네트워크 연결을 처리한다. 인터넷 슈퍼서버라고 부르는 inetd이다.

  1. 시스템 시동 과정에서 init은 셸을 하나 띄워서 셸 스크립트 /etc/rc를 실행 (여러 데몬 중 inetd실행)
  2. inetd의 부모 프로세스는 init이 된다.
  3. inetd는 호스트에 TCP/IP 연결 요청이 들어오길 기다린다.
  4. 자신이 처리할 연결 요청이 들어오면 inetd는 fork-exec를 이용해서 적절한 프로그램을 실행
  5. telnet 클라이언트는 호스트이름으로 지정된 호스트에 대해 TCP 연결
  6. 서버는 TCP 연결 위에서 TELNET 응용 프로토콜을 이용해서 자룔르 주고받는다.
  7. telnetd 프로세스는 유사 터미널 장치를 열고 fork를 이용해서 두 개의 프로세스로 갈라진다.
  8. 부모는 계속해서 네트워크를 통한 통신을 처리
  9. 자식은 exec로 login 프로그램을 실행

Mac OS X의 네트워크 로그인

Mac OS X는 부분적으로 FreeBSD에 기초한 것이기 때문에 거의 동일하다. 단 Mac OS X에서는 telnet 데몬이 launchd에 의해 실행된다.

 

Linux 네트워크 로그인

Linux의 네트워크 로그인은 BSD와 본질적으로 같으나, 일부 배포판은 inetd 대신 확장된 xinetd프로세스를 사용하고 실행한 네트워크 서비스들을 좀 더 세밀하게 제어할 수 있는 수단을 제공한다.

 

Solaris의 네트워크 로그인

Solaris의 네트워크 로그인 과정은 BSD나 Linux와 거의 동일하다. Solaris의 버전은 SMF의 재시작 데몬으로서 실행된다는 점이 다르다.

 

 

9.4 프로세스 그룹

각 프로세스는 자신의 프로세스 ID를 가지며, 또한 하나의 프로세스 그룹에 속한다.

프로세스 그룹은 하나 이상의 프로세스들의 집합으로 같은 작업에 연관되어 있으며 같은 터미널로부터 신호를 받을 수 있는 프로세스들으 하나의 그룹으로 묶인다. 각 프로세스 그룹에는 고유한 프로세스 그룹 ID가 있다.

getpgrp 함수는 호출 프로세스의 프로세스 그룹 ID를 돌려준다.

#include <unistd.h>
pid_t getpgrp(void);

반환값: 호출 프로세스의 프로세스 그룹 ID

getpgid라는 함수는 pid인수를 받아서 그 프로세스의 프로세스 그룹 ID를 돌려준다.

#include <unistd.h>
pid_t getpgid(pid_t pid);

반환값: 성공 시 프로세스 그룹 ID, 오류 시 -1

pid가 0이면 함수는 호출 프로세스의 프로세스 그룹 ID를 돌려둔다.

getpgid(0); 와 getpgrp(); 동등하다.

 

프로세스 ID가 프로세스 그룹 ID와 동일한 프로세슨 프로세스 그룹 리더다.

 

setpgid 함수는 프로세스가 기존의 프로세스 그룹에 들어가거나 새 프로세스 그룹을 만들 때 호출한다.

#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);

반환값: 성공 시 0, 오류 시 -1

9.5 세션

세션(session)은 하나 이상의 프로세스 그룹들의 집합이고, 세 개의 프로세스 그룹으로 구성된다.

셸 파이프라인을 통해서 프로세스들이 하나의 프로세스 그룹을 형성한다.

 

프로세스가 새 세션을 만들 때에는 setsid를 호출한다.

#include <unistd.h>
pid_t setsid(void);

반환값: 성공 시 프로세스 그룹 ID, 오류 시 -1

이 함수는 호출자가 이미 프로세스 그룹 리더이면 오류를 반환한다.

 

getsid 함수는 프로세스가 속한 세션의 세션 리더의 프로세스 그룹 ID를 돌려준다.

#include <unistd.h>
pid_t getsid(pid_t pid);

반환값: 성공 시 세션 리더의 프로세스 그룹 ID, 오류 시 -1

만일 pid가 0이면 getsid는 호출 프로세스의 세션 리더의 프로세스 그룹 ID를 돌려준다.

9.6 제어 터미널

세션과 프로세스 그룹은 다음과 같은 속성을 가진다.

  • 세션에 하나의 제어 터미널(controlling terminal)이 연결 될 수 있다.
  • 제어 터미널과의 연결을 확립한 세션 리더를 제어 프로세스라고 부른다.
  • 세션 안의 프로세스 그룹들은 하나의 전경(foreground) 프로세스 그룹과 하나 이상의 배경(background) 프로세스 그룹들로 나뉜다.
  • 세션에 제어 터미널이 있으면 그 세션에는 반드시 하나의 전경 프로세스 그룹이 존재한다.
  • 터미널의 가로채기 키(보통 DELETE나 Ctrl-C)를 누르면 전경 프로세스 그룹의 모든 프로세스에 가로채기 신호가 전송된다.
  • 터미널의 중지(quit) 키(보통 Ctrl-\)를 누르면 전경 프로세스 그룹의 모든 프로세스에 중지 신호가 전송된다.
  • 네트워크가 끊어졌음을 터미널 장치가 인식하면 제어 프로세스(세션 리더)에 단절(hang-up: 연결 끊기) 신호가 전송된다.

9.7 tcgetpgrp, tcsetpgrp, tcgetsid 함수

전경 프로세스 그룹을 조회하고 설정하는 함수들은 다음과 같다.

#include <unistd.h>
pid_t tcgetpgrp(int fd);

반환값: 성공 시 전경 프로세스 그룹의 프로세스 그룹 ID, 오류 시 -1
#include <unistd.h>
int tcsetpgrp(int fd, pid_t pgrpid);

반환값: 성공 시 0, 오류시 -1

tcgetpgrp 함수는 fd에 대해 열려 있는 터미널에 연관된 전경 프로세스 그룹의 프로세스 그룹ID를 돌려준다.

제어 터미널을 가진 프로세스는 tcsetpgrp를 호출해서 전경 프로세스 그룹을 설정할 수 있다.

 

tcgetsid 함수를 호출하면 특정 제어 터미널에 연관된 세션 리더의 프로세스 그룹 ID를 알수 있다.

#include <termios.h>
pid_t tcgetsid(int fd);

반환값: 성공 시 세션 리더의 프로세스 그룹 ID, 오류 시 -1

제어 터미널을 관리해야 하는 응용 프로그램이라면 이 tcgetsid를 이용해서 제어 터미널의 세션 리더의 프로세스 그룹 ID(즉 세션 ID)를 알아내면 된다.

 

9.8 작업 제어

작업제어 기능을 이용하면 하나의 터미널에서 여러 개의 작업을 시작할 수 있다.

작업 제어를 위한 조건 3가지

  1. 작업 제어를 지원하는 셸이 있어야 한다.
  2. 커널의 터미널 구동기가 작업 제어를 지원해야 한다.
  3. 커널이 특정 작업 제어 신호들을 지원해야 한다.
$make all > Make.out &
[1] 1475
$pr *.c | lpr &
[2] 1490
$
[2] + Done    pr *.c | lpr &
[1] + Done    make all > Make.out &

 

  • 가로채기 문자 (보통은 DELETE 나 Ctrl-C)는 SIGINT 신호를 발생한다.
  • 중지 문자(보통은 Ctrl-\)는 SIGQUIT 신호를 발생한다.
  • 일시정지 문자(보통은 Ctrl-Z)는 SIGTSTP 신호를 발생한다.
728x90

댓글