Study/TIL(Today I Learned)

24.02.15 간단한 정리, 백준

에린_1 2024. 2. 15. 23:39
728x90

간단한 정리

8. 예외적인 제어흐름

  • 시스템들은 또한 내부 프로그램 변수에 의해 표시되지 않으며, 프로그램의 실행과는 반드시 관련되어 있지 않은 시스템의 상태변화에도 반응할 수 있어야 한다.
  • 현대의 시스템들은 제어흐름의 갑작스러운 변화를 만드는 방법으로 이런 상황에 반응한다.
  • 일반적으로 이와 같은 급격한 변화를 예외적인 제어흐름(ECF : Exceptional Control Flow)라고 한다.

8.1 예외상황

  • 예외상황은 부분적으로 하드웨어와 운영체제에 의해 구현된 예외적인 제어흐름의 한 가지 형태이다.
  • 예외상황은 어떤 프로세서 상태의 변화에 대한 대응이다.
    • 상태의 변화 → 이벤트라고 한다.
  • 프로세서가 이벤트가 발생했다는 것을 감지하면, 예외테이블이라고 하는 점프테이블을 통해서 이 특정 종류의 이벤트를 처리하기 위해 특별히 설계된 운영체제 서브루틴(예외처리 핸들러)으로 간접 프로시저 콜을 하게 된다.

8.1.1 예외처리

  • 한 시스템 내에서 가능한 예외상황의 종류마다 중복되지 않은 양의 정수를 예외번호로 할당하고 있다. 시스템 부팅시 운영체제는 예외 테이블을 할당하고 초기화 한다. 엔트리 k가 예외상황 k에 대한 핸들러를 갖는다.
  • 런타임에 프로세서가 이벤트를 감지하고 대응되는 예외번호를 결정한다. 프로세서는 그 후 예외 테이블의 엔트리 k를 통해 간접 프로시저 콜을 하는 방법으로 예외상황을 발생시킨다.
  • 예외상황과 프로시저의 중요한 차이
    • 제어가 사용자 프로그램에서 커널로 전환하고 있을 때 모든 아이템은 사용자 스택 위가 아니라 커널 스택위로 푸시된다.
    • 예외 핸들러는 커널모드에서 돌아간다.
  • 예외의 종류
    • 비동기 : 인터럽트
    • 동기 : 트랩, 오류, 중단

인터럽트

  • 프로세서 외부에 있는 입출력 디바이스로 부터의 시그널의 결과로 비동기적으로 발생한다.
    • ex) ctrl+c, ctrl + alt + f4, 컴퓨터 리셋

트랩과 시스템 콜

  • 의도적인 예외상황, 어떤 인스트럭션을 실행한 결과로 발생한다.
  • 트랩의 가장 중요한 사용은 시스템 콜이라고 알려진 사용자 프로그램과 커널 사이의 프로시저와 유사한 인터페이스를 제공한다.
  • 시스템 콜은 커널모드에서 돌아간다.
    • ex) system calls, break point traps
  • 시스템 콜
    • 운영체제의 커널이 제공하는 서비스에 대하여, 응용 프로그램의 요청에 따라 커널에 접근하기 위한 인터페이스다.
    • 각 시스템 콜은 오프셋에 대응되는 유일한 정수를 가진다.

오류(fault)

  • 핸들러가 정정할 수 있을 가능성이 있는 에러조건으로부터 발생한다.
  • 핸들러가 에러를 정정할 수 있다면, 제어를 오류를 발생시킨 인스트럭션으로 돌려주어서 거기서부터 재실행한다. 그렇지 않다면 핸들러는 커널 내부의 abort 루틴으로 리턴해서 오류를 발생시킨 응용 프로그램을 종료한다.

중단(abort)

  • 하드웨어 오류 같이 복구 불가능한 치명적인 에러에서 발생한다.
  • 핸들러는 응용 프로그램으로 제어를 리턴하지않고 종료하는 루틴으로 반환한다.

8.2 프로세스

  • 프로세스는 운영체제가 만들어주는 실행 프로그램의 인스턴스다.
  • 시스템 내의 프로그램은 어떤 프로세스의 문맥(context)에서 돌아간다.
  • 문맥은 프로그램이 정확하게 돌아가기 위해 필요한 상태로 구성된다.
  • 사용자가 실행 목적 파일의 이름을 쉘에 입력해서 프로그램을 돌릴 때마다 쉘은 새로운 프로세스를 생성하고, 실행 목적 파일을 새로운 프로세스의 문맥에서 실행한다.

프로세스에서 제공하는 추상화

  • 논리적 제어흐름 : 우리의 프로그램이 프로세서를 혼자서 사용한다는 착각이 들게하는 독립적인 제어흐름
  • 사적인 주소공간 : 프로그램이 혼자서 메모리를 사용한다는 착각을 제공하는 사적 주소공간

논리적 제어흐름

  • 하나의 프로세서를 사용해서 여러 프로세스들이 교대로 돌아간다.
  • 각 프로세스는 자신의 흐름이 일 부분을 실행하고 나서, 다른 프로세스들로 순서를 바꾸어 실행하는 동안 일시적으로 정지 된다.

동시성 흐름

  • 자신의 실행시간이 다른 흐름과 겹치는 논리흐름을 동시성 흐름이라고 부른다.
  • 그들의 실행시간이 중첩되면 동시에 실행된다고 한다.

문맥전환

  • 한 개의 프로세스에서 다른 프로세스로 제어흐름이 넘어가는 것
  • 운영체제 커널은 예외적인 제어흐름의 상위 수준 형태인 문맥전환을 사용하여 멀티태스킹을 구현한다. 커널은 프로세스가 실행되는 동안의 어떤 시점에 현재 프로세스를 선점(일시적으로 정지)하고, 이전에 선점된 프로세스를 다시 시작할 것을 결정할 수 있다.
  • 이런 결정을 스케줄링이라고 하며, 스케줄링은 스케줄러라고 부르는 커널 내부 코드에 의해 처리된다.
  • 문맥전환은 커널이 사용자 대신해서 시스템 콜을 실행하고 있을 때 일어날 수 있다. 또한 문맥전환은 인터럽트의 결과로 발생할 수 있다.

프로세스의 제어

  • 프로세서를 제어하기 위한 많은 시스템 콜
    • 프로세스 ID 가져오기
    • 프로세스 생성 & 종료
    • 자식 프로세스 제거
    • 프로그램의 로딩과 실행

프로세스 ID가져오기

  • 각각의 프로세스는 고유한 ID(PID)를 갖는다.
  • getpid 함수는 호출하는 함수의 PID를 리턴한다.

프로그램 생성 & 종료

  • 생성 - fork
    • 부모 프로세스는 fork 함수를 불러서 자식 프로세스를 생성한다. 완벽하게는 아니지만 부모와 거의 동일하다. 가장 중요한 차이는 서로 다른 PID를 가진다
    • fork 함수는 두 번 리턴한다
    • 호출한 부모에게 자식의 PID를 리턴한다. 자식에게는 0을 리턴한다.
  • 종료 - exit

좀비 프로세스

  • 종료 되었지만 아직 처리되지 않은 프로세스
  • 자식 프로세스가 부모 프로세스보다 먼저 종료되는 경우 해당 자식 프로세스는 좀비 프로세스가 된다.
  • wait함수를 통해 처리해줘야 한다.

고아 프로세스

  • 부모 프로세스가 자식 프로세스보다 먼저 종료되는 경우, 해당 자식 프로세스는 고아 프로세스라고 한다. 이 경우 커널에 의해 init 프로세스에게 입양된다.
    • init프로세스는 PID 1번이며, 결코 종료되지 않는다.
    • 만일 어떤 부모 프로세스가 자신의 좀비 자식들을 소거하지 않고 종료하려면, 커널은 이 init 프로세스가 이들을 소거하도록 한다.

새 프로그램의 로딩과 실행 - execve

  • 현재 프로그램의 컨텍스트 내에서 새로운 프로그램을 로드하고 실행한다.
  • 파일 이름을 찾을 수 없는 에러가 있는 경우에만 호출하는 프로그램으로 리턴한다.
  • 이외의 경우 execve는 절대 리턴하지 않는다.

프로그램 vs 프로세스

  • 프로그램은 코드와 데이터가 합쳐진 것으로, 디스크 상에 목적파일이나 주소공간에 세그먼트로 존재 할 수있다.
  • 프로세스는 실행 중에 있는 프로그램의 특정 사례로서 프로그램은 항상 어떤 프로세스의 컨텍스트 내에서 돌아간다.
    • ex)fork 함수는 부모의 복제인 새로운 자식 프로세스에서 동일한 프로그램을 실행시킨다.
    • ex)evecve는 새로운 프로그램을 현재 프로세스의 컨텍스트 내에서 로드하고 실행한다. 따라서 execve로 실행되는 새로운 프로그램은 기존 프로세스와 동일한 PID를 가진다.

시그널

  • 이벤트가 시스템에 발생했다는 것을 프로세스에게 알려주는 짧은 메세지
  • 시그널은 예외상황과 인터럽트를 커널에서 추상화한 개념으로써 대부분의 경우 커널이 프로세스에게로 시그널을 보내주는 형태를 가진다.

시그널 송신

  • 커널은 목적지 프로세스의 컨텍스트 내에 있는 일부 상태를 갱신해서 시그널을 목적지 프로세스로 보낸다.

시그널 수신

  • 목적지 프로세스는 전달된 신호에 대해서 커널이 어떤 방식으로든 반응하여야 할 때 시그널을 수신한다고 한다.

Pending

  • 송신 했지만 아직 수신되지 않은 시그널을 펜딩 시그널이라고 한다.
  • 어느 특정한 시그널에 대해서는 최대 한 개의 시그널 만이 존재할 수 있다.

프로세스 그룹

  • 모든 프로세스는 정확히 한 개의 프로세스 그룹에 속하며, 이것은 양수 pgID로 식별한다.
  • 기본적으로 자식은 부모와 같은 프로세스 그룹에 속하며, 쉘은 각각 job마다 별도의 프로세스 그룹을 만든다.

시그널 받기

  1. 커널은 프로세스 p에 대해 블록되지 않은 펜딩 시그널 집합을 체크한다.
  2. 만일 하나라도 존재하는 경우 p가 시그널 k를 수신해서 시그널 처리 작업을 수행한다.
  3. 1-2 반복 집합이 0이 될 때 까지
  4. 집합이 빈다면 커널은 제어를 p의 다음 인스트럭션에게 전달한다.

핸들러의 중단

  • 핸들러는 다른 핸들러에 의해 중단될 수 있다.
  • 핸들러 실행 도중 다른 핸들러가 실행되더라도, 모든 작업을 마친 후에는 되돌아와서 나머지 작업을 수행한다.
  • 마지막으로 응용 수준에서 C프로그램은 정상적인 콜/리턴 스택 방식과 분기를 통과해서 하나의 함수에서 다른 함수로 직접 분기하기 위해 비지역성 점프를 사용할 수 있다.

백준

10813

#include <bits/stdc++.h>
using namespace std;

int main()
{
	ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
	int n, m, temp;
	cin >> n >> m;
	vector<int> a(n);
	for (int i = 0; i < n; ++i)
	{
		a[i] = i + 1;
	}
	for (int x = 0; x < m; ++x)
	{
		int i, j;
		cin >> i >> j;
		temp = a[i-1];
		a[i-1] = a[j-1];
		a[j-1] = temp;
	}
	for (int i = 0; i < a.size(); ++i)
	{
		cout << a.at(i)<<" ";
	}
	return 0;
}

10811

#include <bits/stdc++.h>
using namespace std;

int main()
{
	ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
	int n, m, temp;
	cin >> n >> m;
	vector<int> a(n);
	for (int i = 0; i < n; ++i)
	{
		a[i] = i + 1;
	}
	for (int x = 0; x < m; ++x)
	{
		int i, j;
		cin >> i >> j;
		for (i; i < j; ++i)
		{
			temp = a[i - 1];
			a[i - 1] = a[j - 1];
			a[j - 1] = temp;
			--j;
		}
	}
	for (int i = 0; i < a.size(); ++i)
	{
		cout << a.at(i)<<" ";
	}
	return 0;
}

 

728x90