티스토리 뷰

knowledge/운영체제

[운영체제] 인터럽트 및 I/O

글을 쓰는 개발자 2022. 8. 1. 20:46
반응형

WIKI

끼어듦, 또는 가로막기란, 마이크로프로세서(CPU)가 프로그램을 실행하고 있을 때, 입출력하드웨어등의 장치에 예외상황이 발생하여 처리가 필요할 경우에 마이크로프로세서에게 알려 처리할 수 있도록 하는 것.
일하고 싶은 자가 일하고 싶다고 관리인에게 말하는 행위
소프트웨어적 인터럽트

리눅스 커널과 같은 운영체제에서 응용 프로그램의 저수준 입출력 함수가 실행되면, 해당 실시간 라이브러리 함수에 의해 소프트웨어 인터럽트가 실행된다.

 

  • 적절한 서비스 루틴으로 제어를 전달
  • 인터럽트 정보를 조사하는 일반적인 루틴을 호출 -> 인터럽트의 고유한 핸들러 호출
  • 빠르게 처리되어야 하므로 루틴의 테이블 이용
  • 테이블을 통하여 이용할 수 있음 (테이블은 하위 메모리에 저장)

인터럽트와 트랩의 차이점

  인터럽트 트랩
주체 하드웨어 소프트웨어

 

인터럽트 벡터

인터럽트 요청이 발생했을 때, CPU는 interrupt stage에서 인터럽트 소스가 무엇인지 그리고 해당 인터럽트 ISR이 어디에 적재되어 있는지 확인해야만 하는데, 이때 CPU는 인터럽트 벡터를 활용

* 참고: ISR (Interrupt Service Routine)

 

 

인터럽트 벡터 테이블

위에서 말한 테이블에 대해서 자세히 알아보자

주기억장치의 특정 영역에 여러 개의 인터럽트에 대한 인터럽트 벡터를 모아놓은 영역

중앙 처리 장치 아키텍처에서 흔한 개념으로서, (인터럽트 요청과 인터럽트 핸들러와 관련된) 인터럽트 벡터들의 테이블.

디스패치 테이블이란 인터럽트 벡터 테이블을 구현하는 방법 중 하나

인터럽트 벡터 테이블은 인터럽트 서비스 루틴의 시작 주소를 찾는 3가지 방법에서 사용된다.

 

프로세스 생명주기

 

프로세스는 '실행 중인 프로그램'이며, 위와 같은 생명 주기를 갖는다. 즉, 생성(new) 이후 종료(terminated) 전까지 준비(ready)와 실행(running)과 대기(waiting) 상태를 반복한다. 실행 중인 프로세서는 interrupt에 의해 수시로 실행 준비 상태(Running Queue)로 전환했다가 다시 실행 상태로 전환한다. 여러 프로세스가 하나의 CPU를 공유할 수 있지만, 특정 순간에는 하나의 프로세스만 CPU를 사용할 수 있기 때문에 이런 메커니즘이 필요하다.
interrupt 없이 열심히 일하던 프로세스도 디스크에서 데이터를 읽어야 할 땐 CPU를 OS를 반환하고 잠시 수면(sleeping)상태에서 I/O가 완료되기를 기다린다. 정해진 OS 함수를 호출하고 CPU를 반환한 채 알람을 설정하고 대기 큐(Wait Queue)에서 잠을 자는 것이다. 
참조: 친절한 SQL 튜닝 책에서

 

Load Average가 보고하는 부하의 정체

하드웨어는 일정 주기로 CPU로 인터럽트(interrupt)라고 하는 신호를 보낸다. 주기적으로 보내지는 신호라는 점에서 '타이머 인터럽트(Timer Interrupt)'라고 한다. 예를 들면, CentOS5 에서 인터럽트 간격은 4ms가 되도록 설정되어 있다. 이 인터럽트마다 CPU는 시간을 진행시키거나 실행 중인 프로세스가 CPU를 얼마나 사용했는지를 계산하는 등 시간에 관련된 처리를 수행한다. 이때 타이머 인터럽트마다 Load Average 값이 계산된다.
커널은 타이머 인터럽트가 발생했을 때 실행가능 상태인 태스크와 I/O 대기인 태스크의 개수를 세어둔다. 그 값을 단위시간으로 나눈 것이 Load Average 값으로 보고된다. 실행가능 상태인 태스크란 다른 태스크가 CPU를 점유하고 있어 계산을 시작할 수 없는 태스크다. 즉, Load Average가 의미하는 부하는,
- 처리를 실행하려고 해도 실행할 수 없어서 대기하고 있는 프로세스의 수
를 말하며, 보다 구체적으로는 다음과 같음을 알 수 있다.
- CPU의 실행권한이 부여되기를 기다리고 있는 프로세스
- 디스크 I/O가 완료하기를 기다리고 있는 프로세스

이것은 분명히 직감과 일치한다. CPU에 부하가 걸릴 것 같은 처리, 예를 들면 동영상 인코딩 등을 수행하고 있는 도중에 다른 동종의 처리를 수행하고자 해도 결과가 늦어지거나 디스크에서 데이터를 대량으로 읽는 동안은 시스템의 반응이 둔해진다. 하편, 키보드 대기 중인 프로세스가 아무리 많다러도 그것을 원인으로 해서 시스템의 응답이 늦는 일은 없다.
Load Average 자체는 두 가지의 부하를 합쳐서 어디까지나 대기 태스크 수만을 나타내는 수치이므로 이를 보는 것만으로는 CPU 부하가 높은지, I/O 부하가 높은지는 판단할 수 없다. 최종적으로 서버 리소스 중 어디가 병목이 되고 있는지를 판단하려면 좀더 자세하게 조사할 필요가 있다.

참조: 대규모 서비스를 지탱하는 기술 책에서

 

기술 블로그를 통해 알아보는 시간

Naver D2: TCP/IP 네크워크 스택 이해하기

https://d2.naver.com/helloworld/47667

NIC가 패킷을 전송할 때 NIC는 호스트 CPU에 인터럽트(interrupt)를 발생시킨다. 모든 인터럽트에는 인터럽트 번호가 있으며, 운영체제는 이 번호를 이용하여 이 인터럽트를 처리할 수 있는 적합한 드라이버를 찾는다. 드라이버는 인터럽트를 처리할 수 있는 함수(인터럽트 핸들러)를 드라이브가 가동되었을 때 운영체제에 등록해둔다. 운영체제가 핸들러를 호출하고, 핸들러는 전송된 패킷을 운영체제에 반환한다.

 

시스템 콜(System Call)

시스템 콜이 발생하면 시스템 콜을 호출한 애플리케이션이 사용하는 file descriptor에 있는 file을 찾는다. Unix 계열 운영체제에서는 socket, 저장을 위한 일반 files system 용 file, 디바이스 등 여러 가지를 모두 file로 추상화한다. 따라서 file 구조체는 최소한의 정보만 포함한다. Socket의 경우 별도 socket 구조체가 소켓 관련 정보를 저장하고, file은 socket을 포인터로 참조한다. Socket은 다시 tcp_sock을 참조한다. tcp_sock은 sock, inet_sock 등으로 세분화되어 있는데, TCP 외의 다양한 프로토콜을 지원하기 위해서다. 일종의 폴리모피즘과 비슷하다고 보면 되겠다.

 

LINE Tech Blog: https://engineering.linecorp.com/ko/blog/how-to-use-kafka-in-line-1/

https://engineering.linecorp.com/ko/blog/how-to-use-kafka-in-line-2/

https://engineering.linecorp.com/ko/blog/how-to-use-kafka-in-line-3/

브로커는 응답을 반환할 때 로컬 디스크에 저장되어 있는 topic의 데이터를 클라이언트 소켓에 복사해야 합니다. 이 처리를 위해 브로커는 'sendfile'이라는 "시스템 콜"을 사용합니다. sendfile이란 리눅스 커널(Linux kernel) 등에서 제공하는 시스템 콜로, 로컬 디스크의 데이터를 클라이언트 소켓에 바로 복사할 수 있게 해 주는 API입니다. 이 API를 사용하면 데이터를 사용자 공간의 메모리에 한 번 복사하는 오버헤드를 막을 수 있어서 매우 효율적이라고 알려져 있습니다. Kafka는 설계상 이 기능에 크게 의존합니다. sendfile을 처리할 때 대상 데이터가 페이지 캐시에 있으면 리눅스 커널이 그 데이터를 클라이언트 소켓에 복사하기만 하면 되는데, 만약 페이지 캐시에 데이터가 없다면 로컬 디스크에서 데이터를 로딩해와야 합니다.

리눅스 커널은 sendfile이  /dev/null 을 목적지로 호출되었을 때는 메모리 복사를 하지 않게 구현되어 있습니다. 그래서 디스크에서 페이지 캐시로는 데이터가 로딩되지만 그 이후 메모리 복사는 진행되지 않는, 저희에게 아주 이상적인 동작이었던 것이죠.

 

Naver D2: https://d2.naver.com/helloworld/8786166

 

시스템 인터페이스

시스템 인터페이스는 운영체제와 환경에 상관없이 사용할 수 있게 제공되는 공통의 인터페이스이다.
시스템 자원(파일, 메모리, 네트워크 연결 등)은 운영체제의 안정성 및 보안과 밀접히 연계되어 있기 때문에 매우 중요하다. 만약 애플리케이션이 시스템 자원에 직접 접근하는 데 아무런 제약이 없다면 상황에 따라 의도하거나 의도하지 않게 다른 애플리케이션이 사용하고 있는 자원을 엉망으로 만들 수 있다. 이로 인해 시스템 크래시가 발생할 수도 있고 민감한 데이터가 유출될 수도 있다.
이러한 문제점을 방지하기 위해 애플리케이션 또는 사용자가 접근할 수 있는 자원을 관리할 필요가 있다. 애플리케이션과 시스템 자원 사이에 위치하는 '커널(kernel)'을 통해 시스템 자원으로 접근을 관리한다. 커널에 대한 작업 요청은 시스템 호출(system call)을 통해 처리된다. 시스템 호출은 애플리케이션이 시스템 자원을 사용하기 위한 커널로 '요청'을 보내는 것을 의미한다. 이때 커널은 애플리케이션과 사용자를 파악해 권한 부여 등을 처리한다.
운영체제마다 시스템 호출을 다르게 해야 한다면 애플리케이션은 운영체제마다 다른 시스템 호출 처리 코드를 포함해야 할 것이다. 일반적으로 대다수의 프로그래밍 언어는 시스템 자원 접근을 위한 기본 라이브러리(standard library)와 컴파일 대상 운영체제에 맞는 시스템 호출 구현체를 제공한다. 이들 구현체는 해당 운영체제의 시스템 API를 사용하도록 구현되어 있다.
시스템 인터페이스는 이처럼 각각 다른 환경에 상관없이 사용될 수 있도록 제공되는 공통의 인터페이스를 의미한다.

Java로 알아보는 Interrupt

https://docs.oracle.com/javase/tutorial/essential/concurrency/interrupt.html

An interrupt is an indication to a thread that it should stop what it is doing and do something else

"인터럽트는 스레드에게 지금 하고 있는 행동 당장 멈추라는 지시"

public class InterruptTest {
    static Logger log = LoggerFactory.getLogger(InterruptTest.class);
    static class MyThread extends Thread {

        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                log.info("interrupt execute {}", Thread.currentThread().getName());
            }
        }
    }

    @Test
    void interruptTest() {
        log.info("current Thread: {}", Thread.currentThread().getName());
        Thread myThread = new MyThread();
        myThread.start();
        myThread.interrupt();
    }
}
21:11:35.136 [Test worker] INFO com.playground.interrupt.InterruptTest - current Thread: Test worker 21:11:35.138 [Thread-3] INFO com.playground.interrupt.InterruptTest - interrupt execute Thread-3

 

 

참고: https://m.blog.naver.com/qbxlvnf11/221354176759

반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함