'리눅스/기본개념 및 용어' 카테고리의 글 목록 :: YJcode

리눅스는 유닉스와 마찬가지로 /proc 파일 시스템을 제공한다. /proc 파일 시스템은 /proc 디렉토리에 마운트된 디렉토리와 파일로 이뤄져있다.

/proc 파일 시스템은 엄연히 따지면 실제로 존재하는 파일은 아니고, 커널 데이터 구조를 파일 시스템상의 파일과 디렉토리 형태에 대응하여 가상으로 파일화 한 인터페이스이다. 많은 이들이 이를 리눅스의 대표적인 특징으로 들고 있다.

/proc 파일 시스템은 다양한 시스템 시스템 속성을 보고 변경할 수 있는 손쉬운 방법을 제공한다. 또한 /proc/PID라는 디렉토리를 통해 커널위에서 운영중인 모든 프로세스에 대한 정보를 확인할 수 있다.

/proc 파일의 내용은 일반적으로 사람이 읽을 수 있는 텍스트 형태이고, 쉘 스크립트로 파싱할 수 있다. 프로그램에서 원하는 파일을 쉽게 열고, 읽거나 쓸 수 있다. 대부분의 경우 /proc 디렉토리의 파일을 수정하기 위해서는 프로세스가 권한 을 가지고 있어야 한다.

클라이언트/서버 응용프로그램이란 2개의 요소 프로세스로 나뉘어 있는 응용 프로그램을 말한다.

 

  • 클라이언트는 설버에게 요청 메시지를 보내어 어떤 서비스를 수행하라고 요청한다.
  • 서버는 클라이언트의 요청을 검사하고, 적절한 동작을 수행한 뒤, 클라이언트에게 응답 메시지를 보낸다.

 

클라이언트와 서버는 요청과 응답으로 이뤄진 확장된 대화에 참여하기도 한다.

일반적으로 클라이언트 응용 프로그램은 사용자와 상호작용하는 데 비해, 서버 응용프로그램은 공유 자원에 대한 접근을 제공한다. 보통 하나 또는 소수의 서버 프로세스와 통신하는 다수의 클라이언트 프로세스가 존재한다.

클라이언트와 서버는 같은 호스트 컴퓨터 내에 존재하기도 하고, 네트워크로 연결된 서로 다른 호스트에 존재하기도 한다. 서로 통신하기 위해 클라이언트와 서버는 IPC통신 메커니즘을 사용한다.

서버는 다음과 같은 다양한 서비스를 구현할 수 있다.

 

  • 데이터베이스나 기타 공유 정보 자원에 대한 접근을 제공
  • 네트워크로 연결된 원격 파일에 대한 접근을 제공
  • 비즈니스 로직을 캡슐화
  • 공유 하드웨어 자원에 대한 접근을 제공
  • 웹페이지를 제공

 

단일 서버 내의 서비스를 캡슐화하면 다음과 같은 여러 이유로 편리하다.

 

  • 효율성 : 같은 자원을 컴퓨터마다 제공하는 것보다 서버가 관리하는 하나의 제원을 제공하는 편이 비용이 덜 든다.
  • 제어, 조정, 보안 : 자원을 한곳에 둠으로써, 서버가 자원에 대한 접근을 조정하거나, 선택된 클라이언트만 접근할 수 있도록 보안 장치를 둘 수 있다.
  • 이종 환경에서 동작 : 네트워크상의 다양한 클라이언트와 서버가 각기 다른 하드웨어와 OS에서 동작할 수 있다.

 

실시간 응용 프로그램은 입력에 대해 빠른 시간 안에 응답해야 하는 응용 프로그램이다. 흔히 그런 입력은 외부 센서나 특수한 입력 디바이스로부터 들어오고, 출력은 외부 하드웨어를 제어하는 형태를 취한다. 실시간 응답을 요구하는 응용프로그램으로는 자동 조립라인, 은행 ATM, 항공기 항법장치 등 많이 있다.

많은 실시간 응용프로그램이 입력에 대해 빠른 응답을 요구하지만, 결정적인 요소는 이벤트 발생 뒤 응답이 특정 데드라인 안에 전달됨이 보장되는지이다.

실시간 응답성을 제공하기 위해서는 , 특히 짧은 응답 시간이 요구될 때, 하부에 있는 운영체제의 지원이 필요하다. 대부분의 운영체제는 그런 지원을 제공하지 않는데, 실시간 응답성이 다중 사용자 시분할 운영체제의 요구사항과 충돌할 수 있기 때문이다. 전통적인 유닉스 구현은 실시간 운영체제가 아니지만, RTOS버전이 만들어졌다. 리눅스의 RTOS 버전도 만들어졌고, 최근의 리눅스는 그 자체로서 실시간 응용 프로그램을 완전히 지원하는 방향으로 발전하고 있다.

POSIX.1b는 실시간 응용 프로그램을 지원하기 위해 비동기 I/O, 공유 메모리, 메모리에 매핑된 파일, 메모리 잠금, 실시간 클록과 타이머, 대체 스케줄링 정책, 실시간 시그널, 메시지 큐, 세마포어 등 여러 확장 기능을 정의하고 있다. 엄격히 실시간으로 인정되지는 않아도, 대부분의 유닉스 구현은 현재 이들 확장 기능의 일부 또는 전부를 지원한다.

'리눅스 > 기본개념 및 용어' 카테고리의 다른 글

/proc 파일 시스템  (0) 2019.09.26
가상 터미널, 날짜와 시간  (0) 2019.09.25
세션, 제어 터미널, 제어 프로세스  (0) 2019.09.24
스레드, 프로세스 그룹, 쉘 작업 제어  (0) 2019.09.23
시그널  (0) 2019.09.23

가상 터미널은 마스터와 슬레이브라는, 연결된 가상 디바이스의 쌍이다. 이 디바이스 쌍은 두 디바이스 사이에서 양방향으로 데이터를 전송할 수 있는 IPC 채널을 제공한다.

가상 터미널의 핵심은 슬레이브 디바이스가 터미널처럼 동작하는 인터페이스를 제공함으로써, 터미널 중심 프로그램이 슬레이브 디바이스에 연결해서 마스터 디바이스에 연결된 또 다른 프로그램을 통해 터미널 중심 프로그램을 구동할 수 있다는 점이다. 드라이버 프로그램의 출력은터미널 드라이버가 수행하는 일반적인 입력과정을 거친 뒤에 슬레이브에 연결된 터미널 중심 프로그램에 입력으로 전달된다. 터미널 중심 프로그램이 슬레이브로 출력하는 것은 모두 드라이버 프로그램으로 전달된다. 다시 말해서 드라이버 프로그램이 보통은 사용자가 일반 터미널에서 수행하는 일을 수행하는 것이다.

가상 터미널은 다양한 응용 프로그램에서 활용되는데, 특히 X윈도우 시스템에서 제공되는 터미널 윈도우의 구현, 텔넷과 ssh같은 네트워크 로그인 서비스를 제공하는 응용프로그램에 주로 쓰인다.

 

프로세스와 관련된 시간에는 두 가지가 있다.

 

  • 실제 시간은 어떤 ㄴ표준 시점으로부터 측정한 시간또는 어떤 정해진 시점, 일반적으로 프로세스의 시작부터 측정한 시간이다. 유닉스 시스템에서 달력 시간은 UTC 1970년 1월 1일부터 흐른 초를 기준으로 측정하고, 영국 그리니치를 지나는 경선에 따라 정의된 시간대에 따라 조정된다. 유닉스 시스템의 탄생일과 가까운 이 날짜를 기원이라고 한다.
  • 프로세스 시간은 CPU 시간이라고도 하는데, 프로세스가 시작된 이래 사용한 CPU시간의 총량이다. CPU시간은 다시 커널모드에서 코드를 실행하는데 소비한 시간인 시스템 CPU시간과ㅣ, 사용자 모드에서 코드를 실행하는데 소비한 시간인 사용자 CPU시간으로 나뉜다.

 

time명령은 파이프라인에 들어 있는 프로세스들을 실행하는데 소요된 실제 시간, 시스템 CPU시간, 사용자 CPU시간을 보여준다.

세션은 프로세스 그룹의 묶음이다. 특징으로는 아래 몇가지를 들 수 있다.

 

  • 세션 내 모든 프로세스는 동일한 세션 ID를 가지고 있다.
  • 세션 리더는 세션을 만든 프로세스이고, 그 프로세스 ID가 세션 ID 가 된다.
  • 세션은 주로 작업 제어 쉘에서 쓰인다.
  • 작업 제어 쉘에서 만든 모든 프로세스 그룹은 쉘과 동일한 세션에 속하고, 쉘은 세션 리더가 된다.

 

세션에는 보통 연관된 제어 터미널이 있다. 제어 터미널은 세션 리더 프로세스가 처음 터미널 디바이스를 열 때 설정된다. 대화형 쉘이 만든 세션의 경우, 제어 터미널은 사용자가 로그인한 터미널이다. 터미널은 최대한 세션의 제어 터미널이 될 수 있다.

제어 터미널을 열면 세션 리더가 그 터미널의 제어 프로세스가 된다. 터미널이 끊어지면 제어 프로세스는 SIGHUP 시그널을 받는다.

어느 싲점에서든 세션 내 프로세스 그룹 중 하나는 포그라운드 프로세스 그룹으로서 터미널에서 입력을 받을 수 있고, 터미널로 출력을 보낼 수 있다. 사용자가 제어 터미널에서 인터럽트 문자나 중지문자를 입력하면, 터미널 드라이버는 포그라운드 프로세스 그룹을 종료시키거나 중지시키는 시그널을 보낸다. 세션은 임의 개수의 백그라운드 프로세스 그룹을 가질 수 있는데 명령끝에 & 문자를 붙여 만든다.

작업 제어 쉘은 모든 작업을 나열하고, 작업에게 시그널을 보내고, 작업을 포그라운ㄷ드와 백그라운드로 전환하는 명령을 제공한다.

현대 유닉스 구현에서 각 프로세스는 여러 개의 스레드를 가질 수 있다. 하나의 프로세스에 하나의 스레드가 존재하는 상태를 싱글 스레드, 두 개 이상의 스레드가 존재하는 상태를 멀티 스레드라고 부르는데, 멀티 스레드는 멀티 프로세스와 본질적으로 거의 동일하다. 단 하나 다른 것이 있는데

 

  • 멀티 프로세스는 각각의 독립적인 변수 환경을 가지고 있다. 따라서 프로세스 A 가 자신의 변수를 변경하더라도 프로세스 B의 변수는 변경되지 않는다. 따라서 프로세스 A 와 B가 통신을 위해서는 시그널과 같은 IPC가 필요하다.
  • 멀티 스레드는 독립적인 스택을 가지고 있으나 전역변수는 서로 공유한다. 즉, 지역변수는 독립적이지만 전역 변수는 서로 공유한다는 점을 이용하여 IPC 없이도 스레드 간의 상호 통신이 가능하다.

 

위의 확실한 한가지 차이 때문에 멀티 프로세서와 멀티스레드는 항상 비교 대상이 된다. 멀티 프로세서는 완전히 독립된 변수 환경을 가지고 있는데 비해, 멀티 스레드는 힙 영역과 전역 변수 영역을 공유한다는 점에서 큰 차이를 가지고 있다.(다만, 스택 영역은 공유하지 않으며, 지역변수는 스택에 쌓이기 때문에 상호 독립적이다.)

이러한 차이로 인해 경우에 따라 멀티프로세스보다 멀티 스레드로 프로그램을 구현하는 것이 좀 더 자연스럽고 간단한 프로그래밍이 되는 경우도 상당히 많이 있다.

 

쉘에서 실행된 각 프로그램은 새로운 프로세스로 시작된다. 예를 들어, 다음과 같은 명령 파이프라인을 실행하기 위해서 쉘은 3개의 프로세스를 만든다

 

$ ls -l | sort -k5n | less

 

본 쉘을 제외한 주요 쉘들은 모두 작업 제어라는 대화형 기능을 제공하는데, 이를 통해 사용자는 여러 명령이나 파이프라인을 동시에 실행하고 조작할 수 있다. 작업 제어 쉘에서 파이프라인 내의 모든 프로세스는 새로운 프로세스 그룹이나 작업에 속하게 된다. 프로세스 그룹 내의 각 프로세스는 동일한 프로세스 그룹 ID를 갖는데, 이 정수는 그룹 내 프로세스 중 하나의 프로세스 ID와 같다.

커널은 프로세스 그룹 내 전체 프로세스를 대상으로 시그널 전달 등 여러가지 동작을 수행할 수 있다. 작업 제어 쉘은 이 기능을 이용해서 사용자가 파이프라인 내 모든 프로세스를 중지시키거나 재개시킬 수 있게 한다.

시그널은 IPC 외에도 많은 분야에서 폭넓게 쓰이고 있다. 시그널은 '소프트웨어 인터럽트'라고도 하며, 프로세스 입장에서 시그널은 외부에서 어떤 예외상황 혹은 이벤트가 발생했음을 알려주는 트리거 역할을 한다.

시그널에는 여러 종류의 시그널이 있는데, 각각의 종류에 따라 서로 다른 이벤트나 상황을 알려준다. 각 시그널의 종류는 각기 다른 정수로 나타내는데, SIGxxxx 형태의 이름으로 정의되어 있다.

 

시그널은 커널이나 프로세스에서 다른 프로세스나 자신에게 보낸다. 예를 들어, 커널은 다름과 같은 상황에서 프로세스에게 시그널을 보낸다.

 

  • 사용자가 키보드에서 인터럽트(대표적으로 control-C)를 입력했을 때
  • 자식 프로세스 중 하나가 종료되었을 때
  • 프로세스가 설정한 타이머(알람)가 만료되었을 때
  • 프로세스가 잘못된 메모리 주소에 접근하려고 할 때

 

쉘에서 kill 명령으로 프로세스에게 시그널을 보낼 수 있다. kill() 시스템 호출도 프로그램 안에서 같은 기능을 수행한다.

 

프로세스가 시그널을 받으면, 시그널에 따라 다음 중 한 가지 동작을 수행한다.

 

  • 시그널을 무시한다.
  • 시그널을 받고 종료된다.
  • 특수 목적 시그널을 받고 재개될 때까지 중지된다.

대부분의 시그널에 대해, 기본 시그널 동작을 받아들이는 대신, 프로그램이 시그널을 무시하거나 시그널 핸들러를 설정할 수 있다. 시그널 핸들러라 함은 프로그래머가 직접 정의한 함수로, 해당 시그널이 전달되면 프로그래머가 정의한 함수가 실행되게 된다. 이 함수는 시그널이 발생한 상황에 맞춰 적절히 동작하도록 구성하여야 한다.

 

시그널이 발생한 시점에서 전달되기까지를 '시그널이 프로세스에 대해 보류 중'이라고 한다. 일반적으로 보류 중인  시그널은 수신 프로세스가 다음에 실행되면 해당 프로세스가 이미 실행 중이면 즉시 전달된다. 하지만 프로세스의 시그널 마스크에 추가함으로써 시그널을 블록 할 수 도 있다. 블록 중인 시그널이 발생되면, 나중에 블록이 해제될 때까지 시그널은 계속 보류 상태에 머물게 된다.

유닉스 I/O 모델의 특징 중 하나로 만능 I/O 개념을 들 수 있다. 디바이스를 비롯한 모든 종류의 파일에 I/O를 수행할 때 동일한 시스템 호출( open(), read(), write(), close()등)을 사용한다는 뜻이다. 따라서 이 시스템 호출을 사용하는 프로그램은 어떤 종류의 파일도 사용할 수 있다.

 

커널이 제공하는 파일은 근본적으로 한가지, 순차적인 바이트의 흐름으로, 디스크 파일, 디스크와 같이 비순차적 접근이 가능한 디바이스의 경우 lseek() 시스템 호출을 통해 임의의 위치로 접근할 수 있다.

여러 응용프로그램과 라이브러리에서 줄바꿈 문자를 텍스트의 한 줄이 끝나고 새 줄이 시작하는 것으로 해석한다. 유닉스 시스템에는 EOF (end-of-file) 문자가 없으며, 파일을 읽었을 때 데이터가 없으면 파일의 끝으로 간주한다.

 

I/O 시스템 호출은 파일 디스크립터를 통해 열려있는 파일을 참조한다. 여기서 파일 디스크립터는 보통 음수가 아닌 작은 정수이며, I/O 대상 파일의 경로명을 인자로 받는 open()을 통해 얻을 수 있으며, 한번 호출된 뒤부터는 파일이 닫힐 때까지 프로세서와 파일 간의 통로 역할을 하여 준다.

보통 프로세스는 쉘에서 실행될 때 열려있는 파일 디스크립터 3개를 물려받는다. 디스크립터 0은 표준 입력, 디스크립터 1은 표준 출력, 디스트립터 2는 표준 에러이다. 대화형 쉘이나 프로그램에서 이들 세 디스크립터는 일반적으로 터미널에 연결되어 있다. stdio 라이브러리에서 이들 디스크립터는 stdin, stdout, stderr 파일에 해당된다.

 

C 프로그래밍을 할때 가장 처음 작성하는 #include <stdio.h>가 사실은 위의 stdio 라이브러리를 사용해서 표준 출력 디스크립터, 즉 stdout을 사용해서 hello,world를 출력하기 위함이다.

리눅스 커널은 하나의 계층적 디렉터리 구조로 시스템의 모든 파일을 관리한다. 이 계층구조의 기초에는 루트 디렉터리가 있고( / ) 모든 파일과 디렉터리는 이 루트 디렉터리 하부에 위치한다.

 

파일 시스템 내 각 파일에는 파일의 종류가 표시되어 있다. 파일 종류에는 일반파일, 디바이스, 파이프, 소켓, 심볼릭 or  하드 링크, 디렉터리 등의 파일이 있다.

 

  • 디렉터리, 링크

디렉터리는 그 안에 파일 이름과 해당 파일로의 참조로 이뤄진 표를 담고 있는 특수한 파일이다. 파일 이름과 참조가 서로 연결된 것을 링크라고 하며, 파일은 여러 링크를 가질 수 있어, 같은 디렉터리나 다른 디렉터리에 여러 이름으로 존재할 수 있다.

디렉터리에는 최소한 다음의 두 가지 엔트리가 존재한다. '.'은 해당 디렉터리 자신을 가리키는 링크이고, '..'은 계층상 바로 상위 디렉터리인 부모 디렉터리를 가리키는 링크이다. 단, 루트 디렉터리( / )에서는 자신보다 상위 디렉터리가 없기 때문에 '..' 또한 자신을 가리키는 링크로 간주된다.

 

  • 심볼릭 링크

하드 링크와 마찬가지로, 심볼릭 링크도 파일의 또 다른 이름을 제공한다. 그러나 일반 링크가 디렉토리 목록 안의 파일 이름-포인터 엔트리임에 비해, 심볼릭 링크는 다른 파일 이름을 담고 있는 특별히 표시된 파일이다.(즉 심볼릭 링크는 디렉터리에 파일 이름-포인터 엔트리로 존재하고, 해당 포인터가 가리키는 파일에 다른 파일의 이름이 문자열로 들어 있다.) 심볼릭 링크가 가리키는 파일을 대상 파일이라고 하고, 흔히 심볼릭 링크가 대상 파일을 '가리킨다', '참조한다'라고 표현한다. 시스템 호출에 경로명을 적으면 대부분의 경우 커널은 자동으로 경로명 안의 각 심볼릭 링크를 역참조 한다. 즉 경로명 안의 심볼릭 링크 이름을 링크가 가리키는 파일 이름으로 교체하면서 따라간다. 이과정은 심볼릭 링크가 가리키는 대상 자체가 다른 심볼릭 링크인 경우 재귀적으로 계속 따라가게 된다.( 단 커널은 심볼릭 링크가 루프를 형성할 때는 대비해 역참조 횟수를 제한하고 있다.) 심볼릭 링크가 존재하지 않는 파일을 가리키면, 댕글링 링크라고 한다.

 

  • 파일 이름

대부분의 리눅스 파일 시스템에서 파일 이름은 최대 255자까지 가능하다. 파일 이름에는 슬래시와(/) 널 문제(\0)를 제외한 모든 문자가 포함될 수 있다. 하지만 알파벳과 숫자, '.', '_', '-'만 사용하는 것이 좋다. 이 65개의 문자 집합을 SUSv3에서는 이식성 있는 파일 이름 문자 세트라고 한다.

이식성 있는 문자 세트 외의 문자는 쉘이나 정규표현식 등에서 특별한 의미로 사용될 수 있으므로 파일 이름에는 사용하지 않는 것이 좋다. 피치 못할 사정으로 파일이름에 이러한 특수문자를 사용할 때는 해당 문자 앞에 이스케이프 문자(\)를 사용하여 이것이 단순히 문자로만 사용된다는 사실을 명시하여 주는 것이 좋다.

마찬가지로 하이픈(-)으로 시작되는 파일 이름도 피해야 한다. 그런 파일 이름은 쉘에서는 옵션으로 판단 될 수도 있기에 자칫하면 오류의 원인이 될 수 있다. 정리하자면

 

  • 파일이름은 최대 255자 까지 사용 가능하다.
  • 알파벳과 숫자, '.' , '_' , '-'만 사용한다.
  • 이외의 특수문자를 사용할 경우 특수문자 앞에 이스케이프 문자(\)를 붙여준다.
  • 파일 이름은 하이픈( - )으로 시작하지 않는다.

와 같이 볼 수 있겠다.

 

경로명은 파일 이름들이 /로 구별되어 나열된 것으로 마지막 / 이전까지를 경로명의 디렉터리 부분이라고 하고, 마지막 / 이후 부분을 베이스 부분이라고 한다. 베이스 부분은 경로명에서 존재할 수도 있지만 그렇지 않을 수도 있다.

경로명은 크게 다음의 두 가지로 나뉜다.

 

  • 절대 경로명 : /로 시작해서 루트 디렉터리를 기준으로 나타내고자 하는 파일의 위치를 표현한다. 항상 루트 디렉터리에서부터 경로를 찾아간다는 점에서 절대 경로명으로 불린다.
  • 상대 경로명 : 현재 디렉터리를 기준으로 나타내고자 하는 파일의 위치를 표현한다. 루트 디렉터리에서 경로를 찾아가지 않기 때문에 /로 시작하지 않고 ./ 혹은 ../로 시작하며 이 중./ 의 경우 생략이 가능하다.

각 프로세스에는 현재 작업 디렉터리가 있다. 이것은 프로세스의 단일 디렉토리 계층구조 내에 있는 '현 위치'이며, 해당 프로세스에서 상대 경로명을 해석할 때의 기준 디렉터리이다.

 

프로세스는 부모 프로세스로부터 현재 작업 디렉터리를 상속받는다. 로그인 쉘의 첫 작업 디렉터리는 사용자의 패스워드 엔트리에 지정된 홈 디렉토리로 설정된다. 쉘의 현재 작업 디렉토리는 cd 명령으로 바꿀 수 있다.

 

각각의 파일에는 파일의 소유자와 그룹을 정의하는 사용자 ID, 그룹 ID가 지정되어 있다. 어떠한 프로세스가 이 파일에 접근하려 할 때, 커널은 프로세스의 UID와 GID를 이 파일의 소유자인지(사용자 ID- UID가 일치하는지), 혹은 그룹의 구성원인지(그룹 ID - GID가 일치하는지), 아니면 둘 다 아니고 그 외의 프로세스인지를 확인하고 그에 따라 접근을 허용할지 여부를 결정한다. 이때, 파일에는 어디까지 파일에 대한 권한을 허용할지에 대한 3가지 접근권한 비트가 명시되어 있는데, 첫 번째는 읽기 권한, 두 번째는 쓰기 권한, 세 번째는 실행 권한에 해당한다.

 

  • 읽기 권한을 가지고 있으면 해당 파일이나 프로그램을 열람할 수 있다.
  • 쓰기 권한을 가지고 있으면 해당 파일을 갱신, 수정할 수 있다.
  • 실행 권한을 가지고 있으면 해당 스크립트나 프로그램을 실행할 수 있는 권한이다.

이 권한은 디렉터리에도 설정할 수 있는데 디렉토리는 그 성격이 조금 다르다. 읽기 권한은 디렉토리의 내용물들, 즉 파일 이름을 나열할 수 있는 권한을 말하고, 쓰기 권한은 디렉토리의 내용물을 바꿀수 있는, 디렉토리 하부에 새로운 디렉토리 혹은 파일을 추가, 제거할수 있는 권한, 실행 권한은 디렉토리 하부에 있는 파일에 접근할 수 있는 권한을 말한다.

 

즉, 특정 파일에 접근하기 위해서는 해당 파일에 대한 접근 권한도 있어야 하겠지만 파일이 해당하는 디렉토리에 대한 실행 권한 또한 가지고 있어야 한다는 것이다. 만약 둘 중 하나라도 충족하지 못한다면 파일에 접근은 커널이 차단하게 된다.

'리눅스 > 기본개념 및 용어' 카테고리의 다른 글

시그널  (0) 2019.09.23
파일 I/O 모델  (0) 2019.09.23
프로세스 간 통신과 동기화  (0) 2019.09.22
정적 라이브러리와 동적 라이브러리  (2) 2019.09.22
메모리 매핑  (0) 2019.09.22

리눅스 시스템은 수많은 프로세스로 이루어져 있고, 이 중 상당수는 상호 독립적으로 동작한다. 하지만 경우에 따라서 프로세스들은 서로 협력해서 목적을 달성하는 경우도 있는데, 이럴 때 이들은 서로 통신하고 서로의 동작을 동기화할 방법이 필요하다.

 

프로세스 간의 통신방법 중한 가지로 서로 협의가 된 디스크의 특정 파일에 정보를 쓰고, 읽는 것이다. 하지만 이러한 방법은 통신방법으로는 너무 느리고 불편하다. 직접 통신을 할 수 있는데 굳이 CPU나 메모리에 비해 한참 속도가 떨어지는 디스크(혹은 SSD와 같은 저장장치)를 사용하여 통신을 할 필요가 없는 것이다. 그래서 리눅스는 다음과 같은 방법으로 통신을 한다.

 

  • 시그널 : 이벤트가 발생했음을 알리는 통신방법
  • 파이프(pipe - | ), FIFO : 프로세스 간에 데이터를 전송해야 할 때 쓰는 통신방법
  • 소켓 통신 : 컴퓨터 안의 프로세스간에 혹은 다른 PC와 네트워크를 통해 프로세스 간에 데이터를 보낼 때 사용
  • 파일 잠금 : 말 그대로 다른 프로세스가 파일을 열람하거나 수정하지 못하도록 독점하는 행위
  • 메시지 큐 : 프로세스 간에 메시지(데이터 패킷)을 교환할 때 사용
  • 세마포어 : 프로세스간의 동작을 동기화할 때 사용
  • 공유 메모리 : 둘 이상의 프로세스가 메모리 일부를 공유할 경우 사용. 이러한 경우 공유 메모리를 사용하고 있는 프로세스들 중 어느 하나의 프로세스가 메모리 일부를 갱신하면, 다른 프로세스도 즉시 갱신된 메모리를 확인할 수 있다.

 

이러한 통신 방법을 IPC(interprocess communication) 방법이라고도 하며, 중복되는 기능들이 많은 이유는 리눅스를 포함한 많은 유닉스 시스템에는 한 가지 통신방식만 있는 것이 아니라 여러 방식으로 변화된 표준들(변종 계통이 많다)이 있고, 이들이 요구하는 표준이 각기 다르기 때문이다. 예를 들어 FIFO와 유닉스 도메인 소켓은 같은 시스템상의 독립된 프로세스 간에 데이터를 교환한다는 점에서 근본적으로 동일한 기능을 수행하지만, 이들이 어디에서 파생되어서 왔는지를 살펴보면 FIFO는 시스템 V, 소켓은 BSD에서 왔다는 점에서 둘 모두를 수용하는 과정에서 FIFO, 소켓통신 모두를 지원하고 있다고 보면 되겠다.

오프젝트 라이브러리는 컴파일된 오브젝트 코드를 담고 있는 파일로, 응용 프로그램이 호출할 수 있는 함수들이 들어 있다. 연관된 함수들을 하나의 오브젝트 라이브러리에 담으면 프로그램 생성과 유지보수가 편리해진다. 현대 유닉스 시스템은 정적 라이브러리와 동적 라이브러리라는 두 가지 오브젝트 라이브러리를 제공한다.

 

 

  • 정적 라이브러리

초기 유닉스 세스템에서는 정적 라이브러리밖에 존재하지 않았다. 정적 라이브러리는 쉽게 말하면, 컴파일된 오브젝트 모듈의 체계적인 묶음이다. 정적 라이브러리의 함수를 쓰기 위해서는, 프로그램을 빌드하는 과정 중 링크 과정에서 해당 정적 라이브러리를 명시함으로써 해당 라이브러리에 포함되어 있는 여러 함수를 프로그램 내 코드에 필요한 정의를 복사해서 넣는다. 이러한 프로그램을 정적으로 링크된 프로그램이라고 한다.

정적 라이브러리를 사용할 경우 해당 라이브러리를 사용하는 여러 프로그램이 중복되는 코드가 여기저기 산재해 있어 커널에 설치되는 프로그램의 수가 많아질 수록, 혹은 여러 프로그램을 동시에 멀티 태스킹을 해야 할 경우, 각각의 프로그램이 필요 이상으로 덩치가 커지는 문제가 있다. 다시 한번 정리해서 말하자면, 정적으로 링크된 각 프로그램들은 라이브러리에서 추출된 오브젝트 모듈들의 복사본을 각각 포함하고 있기 때문에, 오브젝트 코드가 중복되어 디스크 공간이 낭비되고, 같은 라이브러리 함수를 쓰는 프로그램들이 동시에 실행될 때 또한 메모리가 낭비된다는 단점이 있다.

또한 정적 라이브러리는 라이브러리 함수를 수정해야 하는 상황이 발생하였을 경우, 정적 라이브러리만 수정하는 것으로 끝나는 것이 아니라 이미 이러한 라이브러리를 사용한 모든 프로그램들을 찾아내서 새로이 링크 작업을 거쳐 실행 파일을 생성하여야 한다는 치명적인 단점이 있으나, 한번 실행파일을 생성하면, 정적 라이브러리가 추후 제거되더라도 이미 생성된 실행파일을 실행하는 데는 아무런 문제가 없다는 장점으로 인해 많은 단점에도 불구하고 아직까지 사용되고 있다.

 

  • 동적 라이브러리

동적 라이브러리는 다른 말로 공유 라이브러리라고도 불리우며 정적 라이브러리의 문제를 해결하기 위해 만들어졌다.

프로그램이 동적 라이브러리와 링크되면, 라이브러리 오브젝트 모듈을 실행 파일로 복사하는 대신, 링커는 실행 실행 파일이 실행 시에 해당 공유 라이브러리가 필요하다는 정보만 기록하여 둔다. 이후 실행 파일이 실제로 실행되어 메모리에 올라가면, 동적 라이브러리에 속한 함수가 필요할 때 라이브러리 정보를 참조하여 실제 동적 라이브러리를 참조하여 필요한 정보를 메모리에 올리게 된다. 이후 다른 프로그램이 실행되며 같은 라이브러리 함수를 참조하고자 하면, 이미 메모리에 존재하는 동적 라이브러리 정보를 참조하여 바로 링크 작업을 하게 된다. 이러한 특성 때문에 공유 라이브러리라고도 불리고 있다. 또한 동적 라이브러리는 수정이 필요할 경우 라이브러리만을 수정하여도 기존의 프로그램들이 수정된 라이브러리를 사용한다는 점에서 유지보수가 매우 쉬운 편에 속한다. 하지만, 그럼에도 불구하고, 수정이 잘못되거나, 삭제될 경우 이 라이브러리를 사용하는 프로그램 또한 오작동, 혹은 실행이 불가능해진다는 단점이 있어서 무조건 정적 라이브러리보다 좋은 것은 아니다.

 

 

커널은 위 두 라이브러리의 장단점을 각 라이브러리 성격에 대비하여, 적절한 방식을 사용하여 라이브러리를 구성하고 있다.

+ Recent posts