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

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

~ldmsys
관리자 (토론 | 기여)님의 2026년 10월 31일 (토) 09:27 판 (콘솔 CR 변환의 전제 명확화)
(차이) ← 이전 판 | 최신판 (차이) | 다음 판 → (차이)

키보드 입력 목차


키보드 이벤트를 읽는 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]

Enter가 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와 프로세스 깨우기 · 키보드 입력 목차