> ## Content Index
> Fetch the complete content index at: https://sudormrf.run/llms.txt
> Use this file to discover other available public pages before exploring further.

# Pseudo Terminal (유사? 터미널)
- URL: https://sudormrf.run/2018/02/22/pseudoterminal/
- Published: 2018-02-22T12:30:32.000Z
- Updated: 2018-02-22T12:32:25.000Z
- Author: park jong hyun
- Tags: Linux, #Import 2026-10-24 03:36

본 포스트는 APUE chap.19 을 참고하여 작성함.

**Pseudo Terminal**은 application 이 보기에는 터미널로 보이지만, 사실 진짜 터미널이 아닌 것이다.  
Pseudo Terminal의 구조, 예시, 사용법에 대해 설명하도록 함.

##### Pseudo Terminal 개요

Pseudo terminal 을 사용하는 프로세스들의 전형적인 구조는 아래와 같다.

![19.1](https://sudormrf.run/content/images/2018/02/19.1.png)

- 부모 프로세스가 pseudo terminal master를 open 하고 fork -> 자식 프로세스는 session을 새로 만듬 -> pseudo terminal slave를 open -> standard input, output, error를 pseudo terminal에 연결 -> exec 실행
- Pseudo terminal slave에 연결된 자식 프로세스는 real terminal에 연결된거처럼 terminal I/O function을 사용할 수 있음
- 부모 프로세스의 output은 자식 프로세스는 input, 자식 프로세스의 output은 부모 프로세스의 input

이렇게만보면 부모가 뭔지, 자식이 뭔지 잘 이해가 안가니 예시를 보자.

**1\. Network Login Servers**

![19.3](https://sudormrf.run/content/images/2018/02/19.3.png)

- 원격 호스트에서 로그인 쉘이 실행되면 위와 같은 구조가 됨
- exec가 두번인 이유는 중간에 login 프로세스가 있어서...
- rlogind는 I/O 멀티플레싱 필요로 함.  
  - rlogin 서버는 다른 입출력 스트림(TCP/IP)도 함께 읽고 씀
  - rlogin이 어떤식으로든 select나 poll 같은 입출력 다중화 사용 혹은 두 프로세스 or 스레드로 나눠져야 함을 의미

**2\. Windowing System Terminal Emulation**

![19.4](https://sudormrf.run/content/images/2018/02/19.4.png)

- 터미널 에뮬레이터는 shell과 Window manager사이에서 중재자 역할로, 각 shell은 각자의 window에서 실행됨
- 터미널 에뮬레이터 창의 크기를 바꾸게 되면, 터미널 에뮬레이터에 알려주고 PTY master에 ioctl의 TIOCSWINSZ 명령을 실행해 부장치쪽 창 크기 변경  
  - ioctl(fd, termios.TIOCSWINSZ, struct.windowsize)
  - 이때 입력받은 크기가 다르면 커널은 SIGWINCH 신호를 PTY slave로 보냄  
  화면을 다시 그려야 하는 응용프로그램은 해당 SIGWINCH를 캐치해 ioctl의 TIOCSWINSZ 명령을 통해 새 크기를 얻은 후 화면을 다시 그림

**3\. Script Program**

대부분 Unix 시스템에서 제공되며 터미널 세션 도중의 모든 input, output를 file에 저장하는 프로그램  
![19.5](https://sudormrf.run/content/images/2018/02/19.5.png)

- shell과 터미널 사이에 자신을 끼워 넣음
- script 실행중에는 PTY slave위에 terminal line discipline에서 나오는 모든 내용을 스크립트 파일에 복사 (typescript)  
  - 사용자가 누른 키들 역시 terminal line discipline을 거처 echo -> 스크립트 파일에 사용자 입력도 기록
  - password는 echo되지 않으므로 스크립트 파일에 패스워드기록은 없음

그 외 책에는 몇가지 예시가 더 있는데, 생략함.

##### Pseudo Terminal 열기

**PTY Master Open**

```
#include <stdlib.h>
#include <fcntl.h>

int posix_openpt(int oflag);
  Returns: file descriptor of next available PTY master if OK, −1 on error

```

- oflag : open함수에 사용되는 bitmask  
  - O\_RDWR : readable-and-writable
  - O\_NOCTTY : 컨트롤링 터미널이 되는것을 방지

**PTY Slave 권한 변경 함수**

```
#include <stdlib.h>
int grantpt(int fd);
int unlockpt(int fd);
  Both return: 0 on success, −1 on error

```

- fd : master fd
- grantpt(fd)  
  - slave 노드의 user ID를 호출자의 real user ID로 설정, 노드 그룹ID를 unspecified 값으로 설정
  - permission은 읽기 및 쓰기를 허용하고 그룹소유자의 쓰기를 허용하는 접근권한설정
- unlockpt 함수는 pseudo-terminal의 slave 에 대한 접근을 허용하는데 사용

**PTY Slave 이름을 가져오는 함수**

```
#include <stdlib.h>
char *ptsname(int fd);
  Returns: pointer to name of PTY slave if OK, NULL on error

```

- fd : master fd
- 해당 fd에 대응되는 slave 의 경로 이름을 돌려줌

**위 함수를 편하게 사용하게 APUE에서 만든 함수**

```
#include "apue.h"
int ptym_open(char *pts_name, int pts_namesz);
  Returns: file descriptor of PTY master if OK, −1 on error
int ptys_open(char *pts_name);
  Returns: file descriptor of PTY slave if OK, −1 on error

```

- ptym\_open : 사용 가능한 pty master open, 호출 성공 시 대응되는 slave 이름을 pts\_name을 통해 알려줌
- ptys\_open : ptym\_open 이후 얻어진 pts\_name을 통해 호출하면 slave 생성  
  - ptym\_open 함수가 버퍼보다 긴 문자열을 복사하는 일이 없도록 하기 위해, 호출자는 버퍼의 길이를 pts\_namesz 인수로 제공해야 함

**구현체**

- ptym\_open 함수를 이용해 pty master를 찾아 열고 얻어진 pts\_name을 통해 ptys\_open으로 pty slave를 오픈

```
#include "apue.h"
#include <errno.h>
#include <fcntl.h>
#if defined(SOLARIS)
#include <stropts.h>
#endif
 
int ptym_open(char *pts_name, int pts_namesz)
{
  char *ptr;
  int fdm, err;
  if ((fdm = posix_openpt(O_RDWR)) < 0)
    return(-1);
  if (grantpt(fdm) < 0) /* grant access to slave */
    goto errout;
  if (unlockpt(fdm) < 0) /* clear slave’s lock flag */
    goto errout;
  if ((ptr = ptsname(fdm)) == NULL) /* get slave’s name */
    goto errout;
 
  /*
  * Return name of slave. Null terminate to handle
  * case where strlen(ptr) > pts_namesz.
  */
  strncpy(pts_name, ptr, pts_namesz);
  pts_name[pts_namesz - 1] = ’\0’;
  return(fdm); /* return fd of master */
 
errout:
  err = errno;
  close(fdm);
  errno = err;
  return(-1);
}
 
int ptys_open(char *pts_name)
{
  int fds;
 
#if defined(SOLARIS)
  int err, setup;
#endif
 
  if ((fds = open(pts_name, O_RDWR)) < 0)
    return(-1);
 
#if defined(SOLARIS)
/*
* Check if stream is already set up by autopush facility.
*/
  if ((setup = ioctl(fds, I_FIND, "ldterm")) < 0)
    goto errout;
  if (setup == 0) {
    if (ioctl(fds, I_PUSH, "ptem") < 0)
      goto errout;
    if (ioctl(fds, I_PUSH, "ldterm") < 0)
      goto errout;
 
    if (ioctl(fds, I_PUSH, "ttcompat") < 0) {
errout:
      err = errno;
      close(fds);
      errno = err;
      return(-1);
    }
  }
#endif
  return(fds);
}

```

##### pty\_fork Function

fork를 통해 자식 프로세스를 만들고 앞의 두 함수를 이용하여 자식 프로세스는 session leader이며 controlling terminal를 가지게 하는 함수

```
#include <termios.h>
pid_t pty_fork(int *ptrfdm, char *slave_name, int slave_namesz,
               const struct termios *slave_termios,
               const struct winsize *slave_winsize);
  Returns: 0 in child, process ID of child in parent, −1 on error

```

- ptrfdm : PTY master의 file descriptor
- slave\_name : slave device name
- slave\_namesz : slave name size
- slave\_termios : 값이 있으면 slave PTY의 line discipline를 해당 구조체로 초기화
- slave\_winsize : 값이 있으면 slave 의 window 사이즈를 해당 구조체로 초기화
- pty\_fork 루틴 (책에 코드 있음. 아래 설명으로 대체)

  - pty\_master를 오픈한 후 fork를 호출
  - 자식은 ptys\_open 호출전 먼저 setsid를 호출해 새로운 세션을 만들어야 함  
    - setsid 호출 시점에서 자식은 프로세스 그룹 리더가 아니므로 다음 세 단계가 수행  
      - 자식을 세션리더로 하는 새 세션이 생성
      - 자식에 대한 새로운 프로세스 그룹 생성
      - 자식과 이전 제어 터미널 사이의 모든 관계가 끊어짐
  - 이후 자식은 termios 구조체와 winsize 구조체를 초기화
  - 표준 input, output, error 복제
  - 부모 프로세스는 pty\_master와 자식 프로세스의 ID를 돌려줌

##### pty Program

APUE는 앞의 pty\_fork 함수를 이용하여 pty프로그램을 만듦. (역시 코드는 책에 있음)

**"pty prog arg1 arg2"** 형태로 실행하여, pseudo terminal에연결된 자신만의 세션에서 실행되도록  
함

pty 프로그램의 코드를 보면,

- pty\_fork 호출 후 자식 프로세스는 필요에 따라 pty-slave echo를 끄고 (set\_noecho()) execvp 호출
- 이후 나머지 모든 argument 들을 자식 프로세스에 넘겨줌
- 부모 프로세스는 필요 시 exit 호출 시 터미널 상태를 복원하는 종료 처리부 등록
- 부모는 표준 입력에서 받은 모든 내용을 pty-master에 복사하고 pty-master에서 온 모든 내용을 표준출력으로 복사
- 하나의 프로세스 내부에서 select와 poll을 사용하거나 여러 Thread를 사용하는것도 가능

##### Using the pty Program

pty 프로그램을 사용하는 예제

**대화식 프로그램 작업제어**

```
pty cat

```

위 명령을 실행하면, 아래와 같은 구조로 실행된다고 함  
![19.13](https://sudormrf.run/content/images/2018/02/19.13.gif)

pty\_fork 하고 exec 해서 cat 만들고, pty부모가 또 fork 해서 pty 자식이 생김.  
이때, cat은 고아 이므로 ctrl+z 가 전달이 되지 않음. 아마 pty 가 먹어버리는 듯??  
이런 작업제어가 잘 되게 하려면, pty에 로직이 따로 필요함.

**script**

```
pty "${SHELL:-/bin/sh}" | tee typescript

```

위 명령을 실행하면 아래와 같은 구조로 실행 됨.

![19.14](https://sudormrf.run/content/images/2018/02/19.14.gif)

이해가 안감... ㅠㅠ 해보니까 되긴 되는데

**그 외**

이 외에도 몇가지 예제가 있는데, 다 똑같음.

##### Advanced Features

- Packet Mode  
  - PTY master가 PTY slave의 상태 변화를 알게함
  - 특정 이벤트가 발생할때 PTY master위에 프로세스가 PTY master를 읽게 만드는데 사용
- Remote Mode  
  - PTY master는 PTY slave에 remote mode를 설정할 수 있음
  - remote mode가 되면 line discipline이 어떤 작업도 수행하지 않음
  - 자체적으로 line editing을 사용하는 프로그램에 사용
- Window Size Changes  
  - PTY master 위에 있는 프로세스는 윈도우 사이즈 변경을 PTY slave의 foreground 프로세스 그룹에게 알려줄 수 있음
- Signal Generation  
  - PTY master 위에 있는 프로세스는 PTY slave의 프로세스 그룹에게 signal를 전송할 수 있음