Pseudo Terminal (유사? 터미널)
본 포스트는 APUE chap.19 을 참고하여 작성함.
Pseudo Terminal은 application 이 보기에는 터미널로 보이지만, 사실 진짜 터미널이 아닌 것이다.
Pseudo Terminal의 구조, 예시, 사용법에 대해 설명하도록 함.
Pseudo Terminal 개요
Pseudo terminal 을 사용하는 프로세스들의 전형적인 구조는 아래와 같다.
- 부모 프로세스가 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
- 원격 호스트에서 로그인 쉘이 실행되면 위와 같은 구조가 됨
- exec가 두번인 이유는 중간에 login 프로세스가 있어서...
- rlogind는 I/O 멀티플레싱 필요로 함.
- rlogin 서버는 다른 입출력 스트림(TCP/IP)도 함께 읽고 씀
- rlogin이 어떤식으로든 select나 poll 같은 입출력 다중화 사용 혹은 두 프로세스 or 스레드로 나눠져야 함을 의미
2. Windowing System Terminal Emulation
- 터미널 에뮬레이터는 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에 저장하는 프로그램
- 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 호출 시점에서 자식은 프로세스 그룹 리더가 아니므로 다음 세 단계가 수행
- 자식을 세션리더로 하는 새 세션이 생성
- 자식에 대한 새로운 프로세스 그룹 생성
- 자식과 이전 제어 터미널 사이의 모든 관계가 끊어짐
- 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
위 명령을 실행하면, 아래와 같은 구조로 실행된다고 함
pty_fork 하고 exec 해서 cat 만들고, pty부모가 또 fork 해서 pty 자식이 생김.
이때, cat은 고아 이므로 ctrl+z 가 전달이 되지 않음. 아마 pty 가 먹어버리는 듯??
이런 작업제어가 잘 되게 하려면, pty에 로직이 따로 필요함.
script
pty "${SHELL:-/bin/sh}" | tee typescript
위 명령을 실행하면 아래와 같은 구조로 실행 됨.
이해가 안감... ㅠㅠ 해보니까 되긴 되는데
그 외
이 외에도 몇가지 예제가 있는데, 다 똑같음.
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를 전송할 수 있음