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

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

~ldmsys
관리자 (토론 | 기여)님의 2026년 10월 31일 (토) 09:00 판 (관리자님이 운영체제 기본/Linux의 키보드 입력/콘솔과 터미널 입력 문서를 운영체제 기본/x86-64 Linux의 i8042 키보드 입력/콘솔과 터미널 입력 문서로 이동했습니다: x86-64 전용 각론의 아키텍처와 장치·부팅 범위를 제목에 명시)

키보드 입력 목차


키보드 이벤트를 읽는 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와 프로세스 깨우기 · 키보드 입력 목차