본문으로 이동
  • 현재 모든 문서는 작성 중입니다. 보시는 데 불편함이 있더라도 양해 부탁드립니다.

운영체제 기본/Linux/x86-64/i8042 키보드 입력/콘솔과 터미널 입력: 두 판 사이의 차이

~ldmsys
문장형 절 제목을 직관적인 명사형으로 통일하고 기존 절 링크 보존
역링크 갱신 완료 후 불필요한 호환 span id 제거
4번째 줄: 4번째 줄:
키보드 이벤트를 읽는 evdev 경로와 프로그램의 표준 입력에서 문자를 읽는 경로는 동일하지 않다. Linux v6.12의 가상 콘솔에서는 input core에 연결된 별도의 <code>kbd_handler</code>가 키 이벤트를 문자로 해석한다. 여기서는 활성 텍스트 콘솔의 키보드 모드가 <code>VC_UNICODE</code>이고, TTY가 기본 N_TTY line discipline(에코·행 편집·읽기 완료 조건을 처리하는 계층)과 canonical(행 단위 입력) 모드를 사용하며, CR을 무시하지 않고 줄바꿈으로 변환하는 설정(<code>ICRNL</code> 켜짐)인 경우를 가정한다. GUI 터미널, raw 모드 프로그램, 리디렉션된 표준 입력에는 이 가정을 그대로 적용할 수 없다.
키보드 이벤트를 읽는 evdev 경로와 프로그램의 표준 입력에서 문자를 읽는 경로는 동일하지 않다. Linux v6.12의 가상 콘솔에서는 input core에 연결된 별도의 <code>kbd_handler</code>가 키 이벤트를 문자로 해석한다. 여기서는 활성 텍스트 콘솔의 키보드 모드가 <code>VC_UNICODE</code>이고, TTY가 기본 N_TTY line discipline(에코·행 편집·읽기 완료 조건을 처리하는 계층)과 canonical(행 단위 입력) 모드를 사용하며, CR을 무시하지 않고 줄바꿈으로 변환하는 설정(<code>ICRNL</code> 켜짐)인 경우를 가정한다. GUI 터미널, raw 모드 프로그램, 리디렉션된 표준 입력에는 이 가정을 그대로 적용할 수 없다.


<span id="키_코드에서_문자_바이트로"></span>
== 키 코드와 문자 바이트 변환 ==
== 키 코드와 문자 바이트 변환 ==
<code>kbd_handler</code>는 <code>.event = kbd_event</code>를 등록한다. input core의 <code>input_handle_events_default()</code>가 이벤트 묶음을 하나씩 이 callback에 전달한다. evdev가 읽은 결과를 콘솔 handler에 다시 보내는 구조가 아니라, input core에서 서로 다른 handler로 갈라지는 구조다.<ref name="input-console-v612">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/input/input.c?h=v6.12 Linux v6.12 drivers/input/input.c: input_handle_events_default(), input_register_handle()]</ref>
<code>kbd_handler</code>는 <code>.event = kbd_event</code>를 등록한다. input core의 <code>input_handle_events_default()</code>가 이벤트 묶음을 하나씩 이 callback에 전달한다. evdev가 읽은 결과를 콘솔 handler에 다시 보내는 구조가 아니라, input core에서 서로 다른 handler로 갈라지는 구조다.<ref name="input-console-v612">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/input/input.c?h=v6.12 Linux v6.12 drivers/input/input.c: input_handle_events_default(), input_register_handle()]</ref>
28번째 줄: 27번째 줄:
예약된 work의 함수는 <code>flush_to_ldisc()</code>다. 따라서 이 i8042 경로에서 IRQ 쪽의 문자 적재와 작업 대기열(workqueue)을 실행하는 커널 worker 스레드의 입력 처리는 다른 실행 문맥이다. worker는 버퍼 mutex로 소비를 직렬화한다. 기본 port callback인 <code>tty_port_default_receive_buf()</code>를 거쳐 <code>tty_ldisc_receive_buf()</code>가 line discipline의 <code>receive_buf2</code>를 호출한다. N_TTY에서는 <code>n_tty_receive_buf2()</code>가 <code>n_tty_receive_buf_common()</code>으로 연결된다.<ref name="tty-buffer-v612"/><ref name="tty-port-v612">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/tty/tty_port.c?h=v6.12 Linux v6.12 drivers/tty/tty_port.c: tty_port_default_receive_buf()]</ref><ref name="ntty-v612">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/tty/n_tty.c?h=v6.12 Linux v6.12 drivers/tty/n_tty.c: n_tty_receive_buf_common(), n_tty_receive_char_special(), n_tty_receive_char_canon(), n_tty_receive_handle_newline(), n_tty_read()]</ref>
예약된 work의 함수는 <code>flush_to_ldisc()</code>다. 따라서 이 i8042 경로에서 IRQ 쪽의 문자 적재와 작업 대기열(workqueue)을 실행하는 커널 worker 스레드의 입력 처리는 다른 실행 문맥이다. worker는 버퍼 mutex로 소비를 직렬화한다. 기본 port callback인 <code>tty_port_default_receive_buf()</code>를 거쳐 <code>tty_ldisc_receive_buf()</code>가 line discipline의 <code>receive_buf2</code>를 호출한다. N_TTY에서는 <code>n_tty_receive_buf2()</code>가 <code>n_tty_receive_buf_common()</code>으로 연결된다.<ref name="tty-buffer-v612"/><ref name="tty-port-v612">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/tty/tty_port.c?h=v6.12 Linux v6.12 drivers/tty/tty_port.c: tty_port_default_receive_buf()]</ref><ref name="ntty-v612">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/tty/n_tty.c?h=v6.12 Linux v6.12 drivers/tty/n_tty.c: n_tty_receive_buf_common(), n_tty_receive_char_special(), n_tty_receive_char_canon(), n_tty_receive_handle_newline(), n_tty_read()]</ref>


<span id="Enter가_read를_완료시키는_이유"></span>
== 정규 모드의 줄 입력과 read 완료 ==
== 정규 모드의 줄 입력과 read 완료 ==
canonical 모드에서는 문자 도착과 읽을 수 있는 줄의 완성이 다르다. 예를 들어 프로그램이 충분한 크기의 버퍼로 blocking read를 호출한 뒤 사용자가 <code>a</code>, <code>b</code>를 입력해도, 문자는 N_TTY의 read buffer에 쌓이지만 아직 줄 경계는 공개되지 않는다. 화면에 echo가 보인다는 사실도 프로그램의 read가 반환했다는 뜻은 아니다.
canonical 모드에서는 문자 도착과 읽을 수 있는 줄의 완성이 다르다. 예를 들어 프로그램이 충분한 크기의 버퍼로 blocking read를 호출한 뒤 사용자가 <code>a</code>, <code>b</code>를 입력해도, 문자는 N_TTY의 read buffer에 쌓이지만 아직 줄 경계는 공개되지 않는다. 화면에 echo가 보인다는 사실도 프로그램의 read가 반환했다는 뜻은 아니다.
42번째 줄: 40번째 줄:
GUI 터미널에서는 보통 evdev 입력을 받은 compositor 또는 디스플레이 서버와 사용자 공간 입력 처리를 거쳐 터미널 에뮬레이터가 PTY master에 바이트를 쓴다. 커널의 <code>pty_write()</code>는 연결된 상대 TTY의 flip buffer로 데이터를 넣는다. 이쪽도 N_TTY를 사용할 수 있지만 앞부분의 콘솔 키맵 경로와는 다르다. 표준 입력은 파일이나 파이프일 수도 있으므로, 모든 <code>read(0, ...)</code> 뒤에 물리 키보드 IRQ가 있다고 볼 수 없다.<ref name="pty-v612">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/tty/pty.c?h=v6.12 Linux v6.12 drivers/tty/pty.c: pty_write()]</ref>
GUI 터미널에서는 보통 evdev 입력을 받은 compositor 또는 디스플레이 서버와 사용자 공간 입력 처리를 거쳐 터미널 에뮬레이터가 PTY master에 바이트를 쓴다. 커널의 <code>pty_write()</code>는 연결된 상대 TTY의 flip buffer로 데이터를 넣는다. 이쪽도 N_TTY를 사용할 수 있지만 앞부분의 콘솔 키맵 경로와는 다르다. 표준 입력은 파일이나 파이프일 수도 있으므로, 모든 <code>read(0, ...)</code> 뒤에 물리 키보드 IRQ가 있다고 볼 수 없다.<ref name="pty-v612">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/tty/pty.c?h=v6.12 Linux v6.12 drivers/tty/pty.c: pty_write()]</ref>


<span id="출처"></span>
== 참고 문헌 ==
== 참고 문헌 ==
<references/>
<references/>


이전: [[운영체제 기본/x86-64 Linux의 i8042 키보드 입력/evdev와 프로세스 깨우기|evdev와 프로세스 깨우기]] · [[운영체제 기본/x86-64 Linux의 i8042 키보드 입력|키보드 입력 목차]]
이전: [[운영체제 기본/x86-64 Linux의 i8042 키보드 입력/evdev와 프로세스 깨우기|evdev와 프로세스 깨우기]] · [[운영체제 기본/x86-64 Linux의 i8042 키보드 입력|키보드 입력 목차]]

2026년 11월 1일 (일) 00:04 판

키보드 입력 목차


키보드 이벤트를 읽는 evdev 경로와 프로그램의 표준 입력에서 문자를 읽는 경로는 동일하지 않다. Linux v6.12의 가상 콘솔에서는 input core에 연결된 별도의 kbd_handler가 키 이벤트를 문자로 해석한다. 여기서는 활성 텍스트 콘솔의 키보드 모드가 VC_UNICODE이고, TTY가 기본 N_TTY line discipline(에코·행 편집·읽기 완료 조건을 처리하는 계층)과 canonical(행 단위 입력) 모드를 사용하며, CR을 무시하지 않고 줄바꿈으로 변환하는 설정(ICRNL 켜짐)인 경우를 가정한다. GUI 터미널, raw 모드 프로그램, 리디렉션된 표준 입력에는 이 가정을 그대로 적용할 수 없다.

키 코드와 문자 바이트 변환

kbd_handler는 .event = kbd_event를 등록한다. input core의 input_handle_events_default()가 이벤트 묶음을 하나씩 이 callback에 전달한다. evdev가 읽은 결과를 콘솔 handler에 다시 보내는 구조가 아니라, input core에서 서로 다른 handler로 갈라지는 구조다.[1]

kbd_event()는 kbd_event_lock 아래에서 EV_KEY를 kbd_keycode()에 넘긴다. 이 함수는 활성 콘솔을 선택하고 눌린 키 상태, Shift 상태, 잠금 상태와 key_maps를 이용하여 keysym을 결정한다. 문자 종류에 따라 k_self()와 k_unicode() 등으로 분기하며, Unicode 모드에서는 to_utf8()가 UTF-8 바이트를 생성한다. 즉 KEY_A만으로 최종 문자가 정해지지 않는다. Shift나 키맵에 따라 결과가 달라지고, 키 해제는 일반 문자 입력으로 추가되지 않는다.[2]

IRQ 문맥과 line discipline 사이의 버퍼

문자 바이트를 넣는 put_queue()의 실제 본문은 짧다.[2]

tty_insert_flip_char(&vc->port, ch, 0);
tty_flip_buffer_push(&vc->port);

여기서 flip buffer는 드라이버의 입력을 TTY 처리 계층으로 넘기는 중간 버퍼다. tty_flip_buffer_push()가 곧바로 사용자 read를 끝내는 것은 아니다. 해당 함수는 commit 위치를 공개하고 다음 작업을 예약한다.[3]

tty_flip_buffer_commit(buf->tail);
queue_work(system_unbound_wq, &buf->work);

예약된 work의 함수는 flush_to_ldisc()다. 따라서 이 i8042 경로에서 IRQ 쪽의 문자 적재와 작업 대기열(workqueue)을 실행하는 커널 worker 스레드의 입력 처리는 다른 실행 문맥이다. worker는 버퍼 mutex로 소비를 직렬화한다. 기본 port callback인 tty_port_default_receive_buf()를 거쳐 tty_ldisc_receive_buf()가 line discipline의 receive_buf2를 호출한다. N_TTY에서는 n_tty_receive_buf2()가 n_tty_receive_buf_common()으로 연결된다.[3][4][5]

정규 모드의 줄 입력과 read 완료

canonical 모드에서는 문자 도착과 읽을 수 있는 줄의 완성이 다르다. 예를 들어 프로그램이 충분한 크기의 버퍼로 blocking read를 호출한 뒤 사용자가 a, b를 입력해도, 문자는 N_TTY의 read buffer에 쌓이지만 아직 줄 경계는 공개되지 않는다. 화면에 echo가 보인다는 사실도 프로그램의 read가 반환했다는 뜻은 아니다.

Enter의 콘솔 처리 함수 fn_enter()는 우선 '\r'를 넣는다. 여기서 가정한 설정에서는 n_tty_receive_char_special()이 ICRNL에 따라 이를 '\n'으로 바꾼다. n_tty_receive_char_canon()은 newline을 만나 n_tty_receive_handle_newline()을 호출한다. 이 함수는 줄 경계 비트를 기록하고 canon_head를 공개한 뒤 wake_up_interruptible_poll(&tty->read_wait, ...)로 독자를 깨운다. 일반 문자 도착마다 canonical read가 완료되는 것이 아니다.[2][5]

Backspace도 하드웨어가 이미 전달한 문자를 사용자 메모리에서 지우는 동작이 아니다. 키맵이 만든 바이트가 TTY의 ERASE_CHAR와 일치하면 eraser()가 아직 확정되지 않은 줄을 편집한다. 따라서 a, b, Backspace, Enter를 입력하면 이 조건에서 읽는 내용은 a\n이다. EOF, 별도 EOL 문자, termios 설정은 다른 완료 조건을 만들 수 있다.[5]

깨어난 프로세스와 GUI 터미널

n_tty_read()는 tty->read_wait에 대기 항목을 넣고, 입력이 부족하면 wait_woken(..., TASK_INTERRUPTIBLE, ...)으로 기다린다. 깨어난 태스크는 스케줄된 뒤 canonical buffer를 읽는다. v6.12에서는 먼저 커널 버퍼로 데이터를 반환하고, 바깥의 iterate_tty_read()가 copy_to_iter()로 사용자 쪽에 복사한다. IRQ 안에서 셸 코드가 실행되는 것이 아니다.[5][6]

GUI 터미널에서는 보통 evdev 입력을 받은 compositor 또는 디스플레이 서버와 사용자 공간 입력 처리를 거쳐 터미널 에뮬레이터가 PTY master에 바이트를 쓴다. 커널의 pty_write()는 연결된 상대 TTY의 flip buffer로 데이터를 넣는다. 이쪽도 N_TTY를 사용할 수 있지만 앞부분의 콘솔 키맵 경로와는 다르다. 표준 입력은 파일이나 파이프일 수도 있으므로, 모든 read(0, ...) 뒤에 물리 키보드 IRQ가 있다고 볼 수 없다.[7]

참고 문헌

이전: evdev와 프로세스 깨우기 · 키보드 입력 목차