티스토리 뷰

1. ioctl

input output control 의 약자이고, 디바이스를 명시하는 시스템 콜의 한 종류이다. 

리눅스에는 시스템 콜이 많이 없기 때문에(300-400개, 많은 것 아닌가?..) 디바이스들이 가지고 있는 모든 유니크한

함수들을 호출할 수 없다. 따라서 드라이버가 유저공간 어플리케이션에게 하여금 명령을 보낼 수 있게 한다. 

하지만, ioctl은 유연하지 않고, 어수선한 경향이 있다. 또한, 불안정하기도 하다. 

예를 하나들면, 프린터(종이 인쇄하는) 장치가 설정 옵션으로 폰트를 가지고 있다면, ioctl을 이용해서 현재

프린터의 폰트를 다른 폰트로 바꿀 수도 있고, 현재 폰트가 무엇인지 받아올 수 있다.

함수 정의는 아래와 같다 

int ioctl(int fd, int request, ...) 

fd : 파일 디스크립터이고, open을 통해 얻어진 것 이다.

request : request code 이다. GETFONT 는 현재 프린터의 폰트를 얻어오고, SETFONT는 폰트를 세팅하는 것이다.

세 번 째 arg : void* 형이며, 2번 째 인자에 따라서, 세 번 째 인자가 있을 수 도 없을 수 도 있다. 예를들면 GETFONT에는

                    인자가 없는 것이 자연스러울 것이다.

여기서, request 인자는 아래와 같이 구성되어 있다.

1. 매직 넘버 - 8비트

2. 시퀀스 넘버 - 8비트

3. 인자 타입 (일반적으로 14비트), 혹은 그 외에 맘대로..

4. 데이터 전송 방향(2비트)

 

tty, pts, ptmx??

ptmx은 /dev/ptmx 이고 major number 5, minor number 2인 캐릭터 파일이다. 일반적으로 mode가 0666이며 

root:root 소유이다. pseudo-terminal의 마스터와 슬레이브 쌍을 생성하기 위해서 사용된다.

프로세스가 /dev/ptmx를 열게되면, pseudo terminal master에 대한 파일 디스크립터와(PTM)  pseudo terminal slave(PTS)

를 취득하고, 이것들이 /dev/pts 디렉터리에 생성된다. 

/dev/ptmx를 open해서 얻어지는 파일 디스크립터는, 이것과 연결되는 PTS와 이것을 소유하는 PTM과 독립적이다.

이 path는 파일 디스크립터를 ptsname에 던져줘서 얻을 수 있다.

 

몰라서 계속 거슬리는 pseudo terminal 이란 도대체 뭘까

https://dev.to/napicella/linux-terminals-tty-pty-and-shell-192e

 

Linux terminals, tty, pty and shell

Demystifying Linux terminals

dev.to

http://www.linusakesson.net/programming/tty/index.php

 

The TTY demystified

The TTY demystified Real teletypes in the 1940s. The TTY subsystem is central to the design of Linux, and UNIX in general. Unfortunately, its importance is often overlooked, and it is difficult to find good introductory articles about it. I believe that a

www.linusakesson.net

좀 긴데, 유익해 보이니 다이제스트 해보겠습니다.

TTY 서브시스템은 리눅스와 유닉스 디자인에 중심이 되는 부분이지만, 그 중요성이 간과되고 있다. 

1896년에(갑자기??) stock ticker라는 것이 개발되었다. 전산기계인데, 멀리 있는 주식 시장에 리얼타임으로 참여하기 위한

긴 전선으로 연결된 기계인 것 이다.(*주: 타자기인데 매우 긴 전선으로 연결되어서 타자를 치면 실시간으로 반영 되는 기계인듯)

시간이 지나고 현대의 컴퓨터의 성능이 늘어남에 따라서 멀티 태스킹이 가능했고, 여러 유저들과 실시간으로 상호작용 하는 것이

가능해졌다. 

요즘에는 이러한 물리 텔레타입라이터는 멸종해버려서 박물관이 아니면 볼 수 없지만, 이야기 하고자 하는 TTY가 바로

이러한 물리기계를 소프트웨어로 구현해놓은 것이다. 

Session management

유저가 여러 프로그램을 동시에 실행하면서, 실행되는 프로그램들과 한 번에 하나씩 상호작용 하고 싶어할 텐데

만약 프로그램이 무한루프로 들어가게 되면, 유저는 이 프로그램을 죽이거나 정지하고 싶어 할 것 이다. 

백그라운드에서 시작된 프로그램들은 터미널에 무언가를 쓰려고 하기 전 까지 실행하고 있을 수 있어야 한다. 

이 프로그램이 정지될 때, 사용자의 입력은 포어그라운드 방향을 향하고 있어야 한다. 이러한 기능들의

운영체제에서의 구현이 바로 TTY driver이다.

운영체제의 프로세스는 "살아있다(실행 컨텍스트를 가지고 있다)" 이것이 무슨 의미냐하면은, 어떠한 행동을 취할 수 있단 것이다.

TTY 드라이버는 살아있지 않다. 객체지향적인 용어로 말하면  TTY 드라이버는 수동적인 객체이다. 몇 개의 데이터 필드와

메소드들이 있지만, TTY가 어떤 행동을 하기 위해서 할 수 있는 행동은 TTY가 가지고 있는 메소드들이 프로세스의 컨텍스트나

커널 인터럽트 핸들러에 의해서 호출되는 수 밖에 없다. 

UART 드라이버, line discipline 인스턴스와 TTYP 드라이버는 TTY 디바이스로서 참조되거나, TTY만 참조되기도 한다.

유저 프로세스는 TTY 장치에 해당하는 /dev 아래의 디바이스 파일을 조작함으로써 TTY 의 동작에 영향을 줄 수 있다.

디바이스 파일에 대한 쓰기 권한들이 필요하며, 유저가 특정한 TTY에 로그인 하면, 유저는 디바이스 파일의 소유자가 되어야 한다.

이러한 일은 전통적인 login 프로그램에 의해서 이뤄지며, 루트 권한으로 실행된다. 

콘솔 서브시스템은 엄격하다고 할 수 있는다, 만약에 터미널 에뮬레이션을 유저 단으로 옮길 수 있다면 훨씬 유연할 것 이다. 

이것이 바로 xterm과 xterm의 사본들이 하는 일이다.

터미널 에뮬레이션을 유저 단으로 옮기는 것을 촉진시키기 위해서 TTY subsystem은 그대로 두고, pseudo terminal 혹은

pty라는 것이 발명 되었다.  그리고 추측했듯이, 새로운 pseudo terminal을 pseudo terminal에서 실행할 때, 상황이 좀 더 복잡해진다.

TTY 드라이버에 대해 조금 더 자세히 살펴보면, UNIX 파일에서는, TTYP 드라이버 파일을 포함해서, ioctl이란 마법을 통해

읽히거나 쓰여질 수 있다. ioctl 요청들은 프로세스들에 의해서 초기화되어야 한다. 그래서 커널이 어플리케이션과 

비동기적으로 통신할 때 사용될 수 없다. 

프로세스들이 서로 마비시키거나 죽음의 시그널을 보내면서 소통하는 커널에서, 프로세스들은 시그널들을 가로채고

상황에 적응하려고 노력하지만 대부분은 그러지 못 한다.

그래서 signal은 커널이 프로세스와 비동기적으로 소통할 수 있게 해주는 대강의 방법인데

UNIX에서의 시그널들은 명확하거나 일반적이다기보다는, 각 각의 시그널이 유일하고 개개로 공부해봐야 한다. 

 

PTY vs TTY

pty는 user land에서 작동하는 프로그램에 의해 에뮬레이팅 된 텔레타이퍼이다. 

tty와 비교해보면, 프로그램이 어디에서 동작하느냐에 따라 tty 인지 pty 인지 갈리는데 

user land의 프로그램을 작동시키는 것은 커널이 아니기 떄문이다. 

커널에서 동작하는 프로그램은 관리자 모드가 있어서 커널이 머신의 하드웨어에 접근할 수 있게 한다.

user land에 있는 프로그램은 대신에 커널과만 상호작용하고 하드웨어와 상호작용하지 않는다

커널 모듈에 뭔가 문제가 있으면  모든 시스템이 제대로 동작하지 못 한다. 반면에 user land의 프로그램에 

뭔가 문제가 생기면 그 프로그램만 영향을 받게 된다. 최악의 경우에는 재부팅만이 시스템을 원상복구 시킬 수 있다

이것은 의심의 여지없이 터미널 에뮬레이션을 user land로 옮기는 이유이다.  

PTY가 존재하는 이유는 터미널 에뮬레이션을 user land로 옮기는 것을 촉진하기 위해서일것이다. 

 

그래서 어떻게 동작하는가?

터미널 에뮬레이터는 커널에게 캐릭터 파일들(PTY master와 PTY slave)를 요청한다. 

마스터 쪽에서는, 터미널 에뮬레이터를 가지게 되고 슬레이브 사이드에서는 shell을 가지게 된다. 

마스터와 슬레이브 사이에는 TTY 드라이버가 자리잡고, PTY 마스터와 슬레이브 사이의 데이터를 주고받도록 한다.

 

터미널을 열면 무슨 일이 일어날까? 

1. 터미널을 에뮬레이팅 하는 GUI가 시작된다(터미널이나 Xterm UI 같은) 

2. UI를 비디오에 그리고 OS에게 pty를 요구한다.

3. bash를 서브 프로세스로 띄운다

4. bash로부터 오는 std input, output 그리고  error가 pty slave에 세팅 된다. 

5. XTerm은 키보드 이벤트를 리스닝하고, 캐릭터들을 pty master로 보낸다. 

6. 그 사이에서 line discipline은 캐릭터와 버퍼를 얻어내고, 엔터가 눌리면 slave로 복사를 해준다. 

7. 엔터를 누르면 TTY 드라이버(단지 커널 모듈인)는 버퍼된 데이터를 pty slave에 쓰는데 관여한다.

8. bash는 최종적으로 캐릭터들을 읽고, bash stdin이 PTY slave에 세팅되어 있다는 사실을 기억한다.

9. 이 시점에서 bash 는 캐릭터를 해석하고 ls를 실행해야한다는 사실을 깨닫게 된다.

10. ls를 fork한다. fork된 프로세스는 bash가 사용하는 것과 동일한 stdin과 stdout, stderr을 갖는다, 당연히 이것은 PTY slave이다.

11. ls 가 실행되고 standard output를 프린트한다.(다시 말하지만 이것은 PTY slave이다) 

12. tty 드라이버는 마스터로에 캐릭터들을 복사한다 

13. XTerm은 pty master로 부터 바이트들을 읽어들이고 UI에 쓴다.

 

unlockpt()

코드에서 /dev/ptmx 를 열어서 PTY master/slave 쌍을 얻은 후에, ptsname을 통해서 PTY slave를 얻는것을 확인했다.

https://linux.die.net/man/4/ptmx

 

ptmx(4): pseudoterminal master/slave - Linux man page

ptmx(4) - Linux man page Name ptmx, pts - pseudoterminal master and slave Description The file /dev/ptmx is a character file with major number 5 and minor number 2, usually of mode 0666 and owner.group of root.root. It is used to create a pseudoterminal ma

linux.die.net

에 따르면 slave를 열기전에 반드시 master file의 descriptor를 grantpt와 unlcokpt에 넘겨야 한다고 말한다.

https://linux.die.net/man/3/unlockpt

unlockpt - psuedo-terminal의 master와 slave 쌍을 언락 해준다는데.. 이 둘은 쌍으로 움직여야만 말이 되는거 아닌가?

이 부분에 대해 찾아보았더니, 아래의 링크에서 직관적이지만 뭔가 이상한 답을 찾을 수 있었다.

https://unix.stackexchange.com/questions/477247/is-pseudo-terminals-unlockpt-tiocsptlck-a-security-feature

 

Is pseudo terminals ( unlockpt / TIOCSPTLCK ) a security feature?

After having opened the master part of a pseude-terminal int fd_pseudo_term_master = open("/dev/ptmx",O_RDWR); there is the file /dev/pts/[NUMBER] created, representing the slave part of he

unix.stackexchange.com

unlockpt는 BSD에서 NOOP이고, 단지 파일 디스크립터가 정말 tty인지 확인하는 동작만 한다고 한다. 

리눅스에서는 여는 것을 방지하기 위한 플래그를 세팅해준다고 한다. 따라서 unlockpt가 사실 하는일은 없지만

old-style의 pseudo-tty 구현으로 남아있는 것 이라고 하고 grantpt도 동일하다고 한다.

과거에 이러한 lock이 필요했던 이유는, 앞에서 말한 /dev/ptmx가 없었던 시절에는 50개의 /dev/pty0, /dev/ttyp0, /dev/ptyp1

이러한 것들이 있었는데, 락을 걸어놓지 않으면 프로세스들끼리 서로 같은 pseudo terminal을 취득하려고 해서 

레이스컨디션이 생길 수 있기 때문에, 무사히 사용하지 않는 pseudo terminal 쌍을 취득하면, 락을 풀기 위함이었다.

 

setctty()

Setctty는 자식의 Ctty 파일 디스크립터의 controlling terminal 을 지정한다. Ctty는 반드시 자식 프로세스의 

디스크립터 번호여야 하고, ProcAttr.Files를 가리키는 인덱스여야 한다. sid가 설정되었을때만 유효하다

 

ctty

https://www.quora.com/In-UNIX-programming-what-is-the-controlling-terminal-What-is-the-intuition-behind-having-background-and-foreground-process-groups

 

In UNIX programming, what is the "controlling terminal"? What is the intuition behind having background and foreground process g

Answer (1 of 3): Processes are grouped together into process groups. Process groups are grouped together into sessions. A session may or may not have a controlling terminal (ctty). The ctty controls processes by sending signals to them, hence the name. A p

www.quora.com

프로세스들은 프로세스 그룹으로 그룹지어지는데, 프로세스 그룹은 세션들로 또 그룹된다. 세션은 controlling terminal을

가질 수 도 있고 아닐 수도 있다. ctty 는 프로세스들에게 시그널을 보냄으로써 컨트롤하는데,  프로세스는 ctty가 보낸 시그널이 아니면

시그널을 받지 못 하게 된다. 하지만 다른 ctty로 부터 동작하는 프로세스의 시그널은 받을 수 있다.

세션에서는 단일 프로세스 그룹이  포어그라운드에 있을 수 있고, 다른 것들은 백 그라운드에 있을 것이다. 

포어그라운드에 있는 프로세스 그룹은 포어그라운드에 있는 것 처럼 생각할 수 있다. 아이디어는 바로 이런데 

포어 그라운드의 프로세스들은 유저가 직접적으로 터미널을 통해서 소통할 수 있기 때문이다. 

쉘을 생각해보면, 쉘을 이용해서 어떤 프로세스를 실행시키면 그 동안에는 그 프로그램이 포어그라운드로 올라가고

쉘이 백그라운드로 내려가게 된다, 그리고 새로운 프로세스가 종료되면 쉘이 다시 포어그라운드로 스스로 올라간다 

여기서 주목할 점은, 쉘이 백그라운드에 있을 때, 어떠한 입력이든 포어그라운드로 가게 된다, 그리고 쉘이 포어그라운드로 복귀한 다음에는

커맨드를 다시 쉘에 입력할 수 있게 된다. 백그라운드 프로세스들은 자신들의 ctty로 부터 읽을 수 없도록 되어있다. 

그리고 아마 쓰기가 허락될 수도 안 될 수 도 있다. 

 

mount와 mount옵션

NS namespace는 proc 을 격리시켜서 프로세스 가시성을 제한하기 때문에

이러한 결과를 얻기 위해서는 mount가 필수적이다. 이 과정에서 필요한 mount 들을 알아보자.

https://man7.org/linux/man-pages/man2/mount.2.html

 

mount(2) - Linux manual page

mount(2) — Linux manual page MOUNT(2) Linux Programmer's Manual MOUNT(2) NAME         top mount - mount filesystem SYNOPSIS         top #include int mount(const char *source, const char *target, const char *filesystemtype, unsigned long mountflag

man7.org

 

mount는 source에 명시된 device를 참조하는(혹은 디렉터리나 파일 혹은 더미 스트링일 수도 있다)  파일시스템 경로이름을 

target에 명시된 장소(디렉터리나 파일)로 attach 한다.

적절한 권한(CAP_SYS_ADMIN)이 있어야 mount를 할 수 있다.

filesystemtype에 들어가는 커널에 의해 지원되는 인자는 /proc/filesystems 에 나열되어 있습니다. 

data 에 해당하는 인자는 파일 시스템마다 다르게 해석됩니다.  

일반적으로 컴마(,)로 구분된 옵션들입니다. mount를 호출하는 것은 제공되는 옵션인 mountflags에 따라서 다양한 타입의 오퍼레이션 중에 하나의 오퍼레이션을 수행합니다. 

- 이미 있는 마운트를 재 마운트 하는 경우 : MS_REMOUNT

- bind 마운트를 하는 경우 :  MS_BIND

- 이미 존재하는 마운트의 propagation 타입을 바꾸는 경우:

  MS_SHARED, MS_PRIVATE, MS_SLAVE, MS_UNBINDABLE 중 하나 

- 이미 존재하는 마운트를 새로운 위치로 옮기는 경우: MS_MOVE

- 새로운 마운트를 하는경우 : 위에서 본 옵션중 어느것도 포함하지 않아야 함

추가적인 mount flag들 

MS_LAZYTIME

- inode timestamp의 변화를 in-memory에서만 저장하므로써,  on-disk 업데이트를 감소시킨다

  on-disk timestamps 는 아래와 같은 경우에만 업데이트 된다

  a) inode가 file timestamp와 무관한 업데이트가 필요할 때 

  b) 어플리케이션이 fsync, syncfs, sync를 employ 할 때  

  c) 삭제되지 않은 inode가 메모리에서 퇴거당할 때 

  d) inode가 디스크에 써진지 24시간이 지났을 때

 이러한 옵션이 상당한 이득이 되는 경우는 preallocated file에 잦은 랜덤 write가 발생하는 경우이다. 

물론, MS_STRICATIME  마운트 옵션이 enable되었을 때이다.(MS_STRICTATIM과 MS_LAZYTIME을 합쳤을 때의 장점은 

stat이 적절하게 업데이트 된 atime을 반환할 때이다. 

MS_NOATIME

- 이 파일시스템(마운트하려는)의 모든 파일들의 access time을 업데이트 하지 않는다 

MS_NODEV

- 이 파일시스템의 디바이스에 접근하는 것을 허용하지 않는다.

MS_NOEXEC

- 이 파일시스템의 프로그램이 실행되는 것을 허용하지 않는다.

MS_NOSUID

-  이 파일시스템에서 프로그램을 실행시킬 때, set-user-ID 와 set-group-ID 비트나 파일 capabilities 를 존중(?honor)하지 않는다.

MS_REC

- MS_BIND와 함께 쓰여서 재귀적인 바인드 마운트를 만들기 위해 사용된다. 

MS_REALTIME

- 파일시스템의 파일이 접근될 때, 현재 atime이 파일의 마지막 수정시간 보다 작거나 같을 때 파일의 마지막 접근 시간(atime)을 업데이트 한다. 

MS_STRICTATIME

- 파일에 접근하면 항상 마지막 엑세스 시간(atime)을 업데이트 해준다. 

Changing the propagation type of an existing mount

mountflags가 MS_SHARED, MS_PRIVATE, MS_SLAVE, MS_UNBINDABLE 중에 하나를 포함한다면

현재 존재하는 마운트의 propagation type 이 변경된다. 위의 플래그가 두 개 이상 사용되면 에러를 반환한다. 

propagation type을 바꾸는 동안 사용할 수 있는 플래그는 MS_REC, MS_SILENT이다.

MS_SHARED

- 해당 마운트 포인트를 공유한다. 이 마운트 포인트에 대한 마운트와 언마운트 이벤트는 즉각적으로, 해당 마운트 포인트의 

peer group에  propagate 한다. 여기서 propagation이 의미하는 바는, 동일한 마운트 혹은 언마운트는 자동적으로 

모든 peer group에 일어난다는 것이다. 이와 반대로, peer group에서 마운트와 언마운트 이벤트의 발생은 이 마운트 포인트로도 전파된다.

MS_PRIVATE

- 해당 마운트 포인트를 private으로 만든다. 마운트와 언마운트 이벤트는 이 마운트 포인트에 propagate하지 않는다.

MS_SLAVE

- 해당 마운트 포인트가 다른 peer group에 속하는 shared mount라면, 이 마운트 포인트를 slave mount로 전환한다.

만약 이 마운트 포인트가 다른 peer group에 속하는 shared mount지만 자신을 제외하고 다른 멤버들이 없다면

이것을 private mount로 전환한다. 그렇지 않다면, 마운트 포인트의 propagation type이 바뀌지 않는 상태로 남는다.

MS_UNBINDABLE

'시스템프로그래밍' 카테고리의 다른 글

clone 과 fork의 차이  (0) 2020.09.24
댓글