- Definition : 시스템을 관리할 뿐 아니라 응용 소프트웨어를 실행하기 위하여 하드웨어 추상화 플랫폼과 공통 시스템 서비스를 제공하는 시스템 소프트웨어이다. 최근에는 실제 하드웨어가 아닌 하이퍼바이저 위에서 실행되기도 한다.
- 시스템 자원(System Resource) = 컴퓨터 하드웨어
- CPU (중앙처리장치) , Memory(DRAM,RAM)
- I/O Devices(입출력 장치)
- Monitor,Mouse,Keyboard,Network
- 저장매체 : SSD,HDD (하드디스크)
컴퓨터 하드웨어는 스스로 할 수 있는 것이 없다!
- CPU : 각 프로그램이 얼마나 CPU를 사용할지를 결정할 수는 없다.
- Memory : 각 프로그램이 어느 주소에 저장되어야 하는지, 어느 정도의 메모리 공간을 확보해줘야 하는지를 결정할 수는 없다.
- 저장매체(HDD,SSD): 어떻게 , 어디에 저장할지는 결정할 수 없다.
- 키보드/마우스 : 스스로 표시할수는 없음.
그래서 운영체제가 필요하다!
- Windows OS, Mac OS, 그리고 UNIX(유닉스)
- UNIX OS
- UNIX 계열 OS - LINUX(리눅스)
- UNIX와 사용법이나, OS 구조가 유사
- LINUX(리눅스) OS
- 프로그래머, 전공자가 사용. (웹 서비스라던지, 서버 관리하는데 필수!)
- UNIX 계열 OS - LINUX(리눅스)
- 다양한 응용프로그램들 역시 운영체제가 실행시켜준다. (office 한글, 카카오톡, 게임 등)
- 프로그램 = 소프트웨어
- 소프트웨어 = 운영체제 , 응용프로그램(엑셀, 파워포인트 등)
- 응용 프로그램 = Application(일반 PC에서의 프로그램) = App (스마트폰에서의 프로그램)
- 운영체제는 응용 프로그램 을 관리
- 응용 프로그램을 실행시킨다.
- 응용 프로그램간의 권한 을 관리해준다.
- 관리자 권한으로 실행
- 응용 프로그램을 사용하는 사용자 도 관리
- 로그인
- 응용 프로그램은 누구나 만들 수 있다.
- 응용 프로그램에 무한 반복문을 넣었을 때
- 응용 프로그램을 잘못 작성해서, 프로그램이 다운.
- 모든 파일 삭제 막기(권한 / 사용자 관리)
- 응용 프로그램이 CPU를 많이 사용할 때 이를 막는다.
그래서 운영체제는 응용 프로그램을 관리.
- 응용 프로그램을 관리한다.
- 시스템 자원(System Resource)를 관리한다.
- 사용자와 컴퓨터간의 커뮤니케이션을 지원
운영체제의 목표 : 사용자가 사용하는 응용 프로그램이 효율적으로, 적절하게 동작하도록 지원
운영체제는 응용 프로그램이 요청하는 시스템 리소스를 효율적으로 분배하고, 지원하는 소프트웨어
컴퓨터를 키면 ? --> 운영체제는 Memory에 올라가게 된다!
모든 프로그램은 메모리에 올라가고 하나씩 CPU에 올라간다. 운영체제도 하나의 소프트웨어이기 때문에 메모리에 올라간 후 실행된다. 운영체제는 HDD,SSD 저장매체에 저장되어 있다.
- 운영체제의 역할: 시스템 자원, 응용 프로그램 관리, 효율적 분배
- 응용 프로그램이란 ? - 소프트웨어(운영체제, 응용 프로그램)
- 1개의 응용 프로그램을 실행시키기도 바빴음.
- 응용 프로그램이 직접 시스템 자원을 제어
프로그램 종류도 많아지고, 사용자도 슬슬 많아지기 시작
- 철수 : 프로그램1 - 예상 실행 시간 12시간
- 영희 : 프로그램2 - 예상 실행 시간 1분
- 실행 순서 : 프로그램1 --> 프로그램2 (결국 영희는 12시간을 기다렸다가 입력을 주어 프로그램2를 실행시켜야함.)
- 실행 순서 : 프로그램2 --> 프로그램1
- 배치 처리 시스템(batch processing system) 출현
- 여러 응용프로그램을 등록시켜놓으면, 순차적으로 실행하는 시스템
- 배치 처리 시스템을 기반으로 운영체제가 출현
- 새로운 개념이 제안됨
- 시분할 시스템(Time Sharing System)
- 멀티 태스킹(Multi Tasking)
운영체제로 구현되지는 않았음.
- 응용 프로그램이 CPU를 사용하는 시간을 잘게 쪼개서, 여러 개의 응용 프로그램을 동시에 실행하는 것처럼 하는 기법
-
시분할 시스템: 다중 사용자를 지원하고, 컴퓨터 응답 시간을 최소화하는 시스템
- 다중 사용자 를 지원한다는 것은 응답시간을 최소화 해야함! (컴퓨터 응답시간: 다음 응용프로그램이 실행되기까지 걸리는 시간)
-
멀티 태스킹: 가능한 CPU를 많이 활용하도록 하는 기능 (시간대비 CPU 사용율을 높이는 것)
- 단일 CPU에서, 여러 응용 프로그램의 병렬 실행을 가능케 하는 시스템
-
보통은 시분할 시스템, 멀티 태스킹을 동일한 의미로 사용함.
멀티 프로그래밍 : 최대한 CPU를 많이 활용하도록 하는 기능 ( 시간대비 CPU 사용율을 높이는 것) ex) 어플리케이션1 이 저장매체에서 파일을 읽어오는 등의 일을 하면서 CPU사용을 멈췄을 때 다른 어플리케이션2을 실행시키다가 어플리케이션1이 CPU를 사용해야될 때가 되면 다시 어플리케이션2 실행을 멈추고 어플리케이션1을 CPU에 올림. 실제로 응용 프로그램에 할당하는 CPU 점유 시간을 쪼개면 쪼갤 수록 CPU 사용율을 높일수 있다!
- 컴퓨터 응답 시간 (response time)이 오래 걸릴 수 있다. (앞 단에 실행시간이 많이 필요로 하는 응용프로그램이 실행될 경우)
- 실행 시간도 오래 걸릴 수 있다. (CPU가 필요없음에도 응용 프로그램이 CPU를 점유할 수 있기 때문)
- 시분할 시스템/ 멀티 태스킹
- 핵심기술 : 시간을 잘게 쪼개서, 여러 응용 프로그램을 실행
- 컴퓨터 응답 시간을 줄일 수 있음 (시분할 시스템, 다중 사용자 지원)
- 전체 응용 프로그램의 실행 시간도 줄일 수 있음 (멀티 프로그래밍)
- 결과적으로, 사용자가 느낄 때에는 여러 응용 프로그램이 동시에 실행되는 것 처럼 보인다. (멀티 태스킹)
- 핵심기술 : 시간을 잘게 쪼개서, 여러 응용 프로그램을 실행
1960년대 후반부 정리
- 시분할 시스템/ 멀티 태스킹
- 멀티 프로그래밍
- 기술 : CPU 시간을 잘개 쪼개기
- 결과 : 다중 사용자 지원, 응용 프로그램 동시 실행
제대로된 운영체제가 나옴
- 미국 AT&T 사의 벨 연구소
- 켄 톰슨, 데니스 리치
- 데니스 리치 : C 언어를 개발
- 1970년대 이전: Assembly 언어로 소프트웨어 개발
- CPU(명령어), Memory(주소)를 기계어로 직접 입력해줘야 했다.
- 컴퓨터 각각의 하드웨어 스펙에 따라 다시 개발해줘야 하는 단점이 있다.
- 프로그래밍 복잡도가 높음.
- 1970년대 C언어가 개발된 후:
- 컴파일러가 등장하면서 하드웨어 스펙에 따라 다시 개발해줄 필요가 없어졌다.
C언어를 기반으로 운영체제를 개발했다.
- 현대 운영체제의 기본 기술을 모두 포함한 최초의 운영체제
- 멀티태스킹, 시분할 시스템, 멀티 프로그래밍
- 다중 사용자 지원
- 멀티태스킹, 시분할 시스템, 멀티 프로그래밍
- 본격적으로 운영체제 중요성 부각
- 현대 운영체제 기술 확립, UNIX OS에 최초 구현
- UNIX : 멀티태스킹, 시분할 시스템, 멀티 프로그래밍 구현
- 개인용 컴퓨터시대
- 1980년대 이전 : 대형 컴퓨터를 여러 명이 접속해서 사용(UNIX)
- 1980년대 ~ : Personal Computer (PC)
- 용어 이해 : CLI 와 GUI
- CLI(Command Line Interface) : 터미널 환경
- GUI(Graphical User Interface) : GUI 환경
- 응용 프로그램 시대 (GUI 환경) , 개인용 컴퓨터 (사용자 증대) + 엑셀 , 워드 프로세서 등 + Windows OS 대중화
- 네트워크 기술 발전 - (World wide web) WWW 인터넷 대중화
- 오픈 소스 운동 활성화 시작 + UNIX 계열 OS + 응용 프로그램 자체 개발, 소스 오픈 + LINUX(리눅스) 운영체제, 소스 오픈, 무료
-
오픈 소스 활성화 + LINUX(리눅스) 운영체제 + Apache(아파치,웹서버) + MySQL(데이터베이스) + 안드로이드, 딥러닝, 데이터 사이언스, IoT 관련
-
가상 머신, 대용량 병렬 처리 등 활성화
- 1950년대 : 운영체제 없음 (프로그램이 시스템 자원 직접 제어)
- 1960년대 : 배치 처리 시스템
- 1970년대 : 시분할 시스템 / 멀티태스킹 시스템 - UNIX OS (C언어)
- 1980년대 : GUI , 개인용 컴퓨터
- 1990년대 : 다양한 응용 프로그램, 인터넷 발달, 오픈 소스 운동 활성화
- 2000년대 : 오픈 소스 활성화, 가상 머신, 대용량 병렬 처리(CPU 코어가 여러개 : 프로그램을 여러개 동시에 실행시키는 것이 가능하게 됨)
- 운영체제는 도서관
- 응용 프로그램을 시민
- 컴퓨터 하드웨어는 책
- 운영체제의 역할
- 시민은 도서관에 원하는 책(자원)을 요청함
- 도서관은 적절한 책(자원)을 찾아서, 시민에게 빌려준다.
- 시민이 기한이 다 되면, 도서관이 해당 책(자원)을 회수한다.
- 운영체제는 응용 프로그램이 요청하는 메모리를 허가하고 분배한다.
- 운영체제는 응용 프로그램이 요청하는 CPU 시간을 제공한다.
- 운영체제는 응용 프로그래밍 요청하는 I/O Devices 사용을 허가/제어한다.
출처 : 위키피디아
- 쉘(Shell)
- 사용자 가 운영체제 기능과 서비스를 조작할 수 있도록 인터페이스를 제공하는 프로그램
- 쉘은 터미널 환경(CLI) , GUI 환경 두 종류로 분류
- API(Application Programming Interface) : 사용자가 아닌 응용 프로그램에게 인터페이스를 제공
- 함수로 제공
- open() : 파일 입출력 등
- 보통은 라이브러리(library) 형태로 제공
- C library (안에 printf, scanf 등의 API가 존재)
- Users 와 Application , Shell이 운영 체제에 인터페이스를 요청할 때 API 가 없으면 불가능하다.
- API를 요청서의 집합으로 볼 수 있음
- Users와 Application을 시민으로 본다면 도서관(OS)에 책(메모리,cpu 등)을 요청하기 위해 신청서(API)를 작성해야한다.
- 함수로 제공
- 시스템 콜 또는 시스템 호출 인터페이스
- 운영체제가 운영체제 각 기능을 사용할 수 있도록 '시스템 콜' 이라는 명령 또는 함수를 제공한다.
- API 내부에는 시스템콜을 호출하는 형태로 만들어지는 경우가 대부분이다.
- Users, Application -> Library or API(각 언어별) -> System Call(운영체제가 제공하는 함수) -> Operating System -> Hardware
- 운영체제를 개발한다.(kernel)
- 시스템 콜을 개발
- C API(library) : 시스템 콜 호출을 위하여
- Shell 프로그램 : 사용자에게 운영체제의 기능과 서비스를 제공하기 위하여
- 응용 프로그램 개발
- 시스템콜을 정의 예
- POSIX API (UNIX 계열),윈도우 API
- Mac OS도 POSIX API를 표준으로 API를 제공하고 있다.
API: 각 언어별 운영체제 기능 호출 인터페이스 함수 시스템콜: 운영체제 기능을 호출하는 함수
- 운영체제는 컴퓨터 하드웨어와 응용 프로그램을 관리한다.
- 사용자 인터페이스를 제공하기 위해 쉘 프로그램을 제공한다.
- 응용 프로그램이 운영체제 기능을 요청하기 위해서, 운영체제는 시스템콜을 제공한다.
- 보통 시스템 콜을 직접 사용하기 보다는, 해당 시스템 콜을 사용해서 만든 각 언어별 라이브러리(API)를 사용한다.
- CPU도 권한 모드라는 것을 가지고 있다.
- 사용자 모드(User mode)
- 커널 모드(kernel mode) : 특권 명령어 실행과 원하는 작업 수행을 위한 자원 접근을 가능케 하는 모드
출처: 위키피디아 커널(kernel)이란? 사전적 의미: 알맹이, 핵심 -> OS의 핵심을 의미 쉘(Shell) 이란? 사전적 의미 : 껍데기
- 두가지 모드
- 사용자 모드(User mode) : 응용 프로그램이 사용 (CPU Protection ring Level 3)
- 커널 모드(kernel mode) : OS가 사용 (CPU Protection ring Level 0 : 커널이 사용 , 1~2 : OS가 사용)
- '파일에서 데이터 가져오기' : 응용 프로그램이 파일에서 데이터 가져오기 명령을 내리면 API 안의 시스템 콜을 통해서 디스크 안의 파일에서 데이터를 찾아 응용 프로그렘에게 반환한다 (커널 영역)
- 커널 모드에서만 실행 가능한 기능들이 있음 (운영체제 내에 구성 해놓는 것)
- 커널 모드로 실행하려면, 반드시 API 내의 시스템 콜을 사용해야 함(거쳐야함) (응용 프로그램이 그 기능을 사용하려면 시스템 콜을 사용하여 커널 모드로 실행해야함)
- 시스템 콜은 운영체제 제공
- 함부로 응용 프로그램이 전체 컴퓨터 시스템을 해치지 못함
- 주민등록등본은 꼭 동사무소 또는 민원 24(정부 사이트)에서 특별한 신청서(시스템 콜)를 써야만 발급 --> 사용자 모드
- 동사무소 직원분들은 특별한 권한을 가지고 , 주민등록등본 출력 명령을 실행 --> 커널 모드
- 응용 프로그래머와 시스템 프로그래머
- 응용 프로그래머 : 운영체제에서 만들어준 API를 가지고 응용 프로그램을 만드는 개발자
- 시스템 프로그래머 : 운영체제/Shell/API/System call 을 개발하는 개발자
#include ~~
int main(){
int fd; // 이까지는 사용자 모드
fd = open("data.txt",O_RDONLY); // 해당 파일은 저장매체에 저장되어 있으므로 접근하려면 커널모드로 접근해야함. (open()이라는 API 시스템콜 호출)
}
#include <unistd.h>
main(){
open()} --> 함수 호출
헤더파일(해당 API(open)의 시스템 콜을 사용하기위해) unistd.h 파일 -> open 선언
커널 영역 : open()이 호출되면 sys_open() (시스템 콜) 함수를 호출한다
- 운영체제는 시스템 콜 을 제공
- 프로그래밍 언어별로 운영체제 기능을 활용하기 위해, 시스템 콜을 기반으로 API 제공
- 응용 프로그램은 운영체제 기능 필요시, 해당 API 를 사용해서 프로그램을 작성
- 응용 프로그램이 실행되서, 해당 운영체제 기능이 필요한 API 를 호출하면 시스템 콜이 호출되서 커널모드로 변경되어 OS 내부에서 해당 명령이 실행되고, 다시 응용 프로그램으로 돌아간다.
- 자동으로 다음 응용 프로그램이 이어서 실행될 수 있도록 하는 시스템 (Batch Processing) = > Queue 와 같은 동작 (First in First out)
- 배치 처리 시스템은 여러 프로그램을 순차적으로 실행시킬 수 있도록 하는 요구사항 때문에 등장함
- 어떤 프로그램은 실행시간이 길어서 , 다른 프로그램이 되기 까지 많은 시간이 소요됨.
- MP3를 들으면서(mp3도 cpu에 올라가서 음악을 재생시키는 동작이 필요함) , 문서 작성을 하고싶을때 ,
- 배치 처리 시스템은 두 가지를 동시에 실행 하는 것이 불가능함.(동시에 여러 응용 프로그램 실행 불가)
- 여러 사용자가 동시에 하나의 컴퓨터를 쓰는 것이 불가능함. (다중 사용자 지원x)
이러한 이유들 때문에 멀티 프로그래밍 / 시분할 시스템이 나왔다.
- 시분할 시스템 : 다중 사용자 지원을 위해 컴퓨터 응답 시간을 최소화 하는 시스템
- 멀티 태스킹 : 단일 CPU에서 여러 응용 프로그램이 동시에 실행되는 것 처럼 보이도록 하는 시스템
- MP3 음악을 들으며 문서 작성을 한다. (아주 짧은 시간(예를 들어 0.001초)에 mp3 파일을 잠깐 실행시키는데 문서 작성에서 키보드를 쳐서 그 딜레이를 캐치할 수 있는 사람은 없다)
- 1000 밀리초(ms) = 1초
- 10 ~ 20ms 단위로도 실행 응용 프로그램이 바뀐다.
- 사용자에게는 동시에 실행되는 것처럼 보인다.
- 출처 : http://donghoson.tistory.com/15
- 멀티 태스킹 : 단일 CPU
- 멀티 프로세싱 : 여러 CPU에 하나의 프로그램을 병렬로 실행해서 실행 속도를 극대화하는 시스템
- 배치 처리 시스템
- 시분할 시스템(다중 사용자 지원, 응답시간 최소화)
- 멀티 태스킹(동시에 실행하는 것 처럼 보이도록)
- 멀티 프로세싱(여러 CPU에 하나의 프로그램을 병렬로 실행시키는 시스템)
-
최대한 CPU를 많이 활용하도록 하는 시스템
- 시간 대비 CPU 활용도를 높임
- 응용 프로그램을 짧은 시간 안에 실행 완료를 시킬 수 있음
-
응용 프로그램은 온전히 CPU를 쓰기보다, 다른 작업을 중간에 필요로 하는 경우가 많음
- 응용 프로그램이 실행되다가 저장매체로 부터 파일을 읽는다.
- 응용 프로그램이 실행되다가 프린팅을 한다.
fd = open("data.txt",O_RDONLY) [1] open() 시스템 콜 호출 -> [2] 커널 모드로 전환 -> [3] open() 함수를 처리하는 sys_open()커널 함수 호출 -> [4] 파일 열기 레벨로 연산 수행(저장매체로 부터 읽음)(파일 읽기 수행 결과 파일 디스크럽터 (fd) 반환) -> [5] 사용자 모드로 전환 -> [6] open()함수 이후의 프로그램을 계속해서 실행
- 응용프로그램이 중간에 파일에 접근을 해야한다면 저장매체 Flash Drive(SSD), Hard Disk(HDD)로 넘어가면서 Access time이 상당히 오래 걸림을 알수 있음.( cycle 단위부터가 다름)
- 그러므로 응용 프로그램이 저장매체에 접근할 때 CPU에게 다른 일을 시키는 것이 CPU 이용률을 높일 수 있음
- 출처 : http://computationstructures.org/lectures/caches/caches.html
- 그래서 컴퓨터 구조에는 저장 매체에 접근할 수 있는 DMA라는 장치를 둬서 저장 매체에는 DMA가 접근해서 데이터를 가져오고 그 동안 CPU는 다른 일을 수행한다!
실제로는 시분할 시스템, 멀티 프로그래밍, 멀티 태스킹이 유사한 의미로 통용된다.
-
핵심
- 여러 응용 프로그램 실행을 가능토록 함
- 응용 프로그램이 동시에 실행되는 것처럼 보이도록 함
- CPU를 쉬지 않고 응용 프로그램을 실행토록 해서, 짧은 시간 안에 응용 프로그램이 실행완료될 수 있도록 함.
- 컴퓨터 응답 시간도 짧게 해서, 다중 사용자도 지원
-
정리
- 시분할 시스템: 다중 사용자 지원, 컴퓨터 응답시간을 최소화 하는 시스템
- 멀티 태스킹: 단일 CPU에서 여러 응용 프로그램을 동시에 실행하는 것 처럼 보이게하는 시스템
- 멀티 프로세싱: 여러 CPU에서 하나의 응용프로그램을 병렬로 실행하게 해서, 실행속도를 높이는 기법
- 멀티 프로그래밍: 최대한 CPU를 일정 시간당 많이 활용하는 시스템
- 실행 중인 프로그램은 프로세스라고 함
- 프로세스 : 메모리에 올려져서, 실행 중인 프로그램
- 코드 이미지(바이너리) : 실행 파일, 예 : ELF format
프로세스라는 용어는 작업,task,job 이라는 용어와 혼용
- 응용프로그램 != 프로세스
- 응용 프로그램은 여러 개의 프로세스로 이루어질 수 있음
- 하나의 응용 프로그램은 여러 개의 프로세스(프로그램)가 상호작용을 하면서 실행될 수도 있음
간단한 C/C++ 프로그램을 만든다면 -> 하나의 프로세스 여러 프로그램을 만들어서, 서로 통신하면서 프로그램을 작성할 수도 있음(IPC 기법)
스케줄러 : 프로세스 실행을 관리
어느 순서대로 프로세스를 실행시킬까?
- 목표
- 시분할 시스템 예: 프로세스 응답시간을 가능한 짧게
- 멀티 프로그래밍 예 : CPU 활용도를 최대로 높여서, 프로세스를 빨리 실행
프로세스가 저장매체를 읽는다든지, 프린팅을 한다던지 하는 작업 없이, 쭉 CPU를 처음부터 끝까지 사용한다.
- 가장 간단한 스케쥴러(배치 처리 시스템)
- FCFS(First Come First Served) 스케쥴러라고도 불림
- SJF(Shortest Job First) 스케쥴러
- 출처 : https://www.slideshare.net/jeiger/ss
- RealTime OS(RTOS) : 응용 프로그램 실시간 성능 보장을 목표로 하는 OS
- 정확하게 프로그램 시작, 완료 시간을 보장 : 공정등에 사용
- Hardware OS, Software OS
- General Purpose OS(GPOS) : 프로세스 실행시간에 민감하지 않고, 일반적인 목적으로 사용되는 OS
- 예 : Windows, Linux 등
- Priority-Based 스케쥴러
- 정적 우선 순위
- 프로세스마다 우선 순위를 미리 지정
- 동적 우선 순위
- 스케쥴러가 상황에 따라 우선순위를 동적으로 변경
- 정적 우선 순위
- 기본적으로 FIFO 스케쥴링 알고리즘을 취하지만, 할당하는 시간이 정해져있어서 시분할 시스템으로 구성된다.
- 출처 : https://www.slideshare.net/jeiger/ss
- 다양한 기본 스케쥴링 알고리즘
- FIFO(FCFS) 스케쥴링 알고리즘 (배치 처리 시스템)
- 최단 작업 우선(SJF) 스케쥴링 알고리즘
- 우선순위 기반 스케쥴링 알고리즘
- 정적 우선 순위, 동적 우선 순위
- Round Robin 스케쥴링 알고리즘
- 시분할 시스템 기반
-
멀티 프로그래밍: CPU 활용도를 극대화하는 스케쥴링 알고리즘
- running state : 현재 CPU에서 실행 상태
- ready state : CPU에서 실행 가능 상태(실행 대기 상태)
- block state : 특정 이벤트 발생 대기 상태(예 : 저장 매체(파일 읽기))
- 선점형 스케쥴러(Preemptive Scheduling)
- 하나의 프로세스가 다른 프로세스 대신에 프로세서(CPU)를 차지 할 수 있음
- 프로세스 running 중에 스케쥴러가 이를 중단시키고, 다른 프로세스로 교체 가능
- 비선점형 스케쥴러(Non-preemptive Scheduling)
- 하나의 프로세스가 끝나지 않으면 다른 프로세스는 CPU를 사용할 수 없음
- 프로세스가 자발적으로 blocking 상태로 들어가거나, 실행이 끝났을 때만, 다른 프로세스로 교체 가능
- FIFO(FCFS),SJF,Priority-based는 어떤 프로세스를 먼저 실행시킬지에 대한 알고리즘
- RoundRobin 은 시분할 시스템을 위한 기본 알고리즘(선점형 스케쥴러)
스케쥴러가 해결해야하는 이슈! 다양하고 복잡한 스케쥴링 알고리즘 필요
- 리눅스 스케쥴러 : O(1),CFS 와 같이 다양한 방식으로 변경 시도 중
- 인터렉티브(쉘), IO, CPU 중심 프로세스로 미리 구분할 수 있다면 보다 개선된 스케쥴링이 가능함
- CPU가 프로그램을 실행하고 있을 때, 입출력 하드웨어 등의 장치나 또는 예외사항이 발생하여 처리가 필요할 경우에 이를 CPU에 알려서 처리하는 기술
어느 한순간 CPU가 실행하는 명령은 하나! 다른 장치와 어떻게 커뮤니케이션을 할까?
- 컴퓨터의 레지스터에는 sp(stack pointer), pc(program counter) 등이 있는데, 이 중 pc는 메인 메모리의 instruction의 주소값을 저장하고 있다.
- pc : 다음에 실행할 명령어의 주소를 기억하고 있는 중앙처리장치의 레지스터 중 하나
- 코드 안에서 프로그램 카운터는 각 각의 코드들의 주소값을 순차적으로 가지게 되는데, CPU는 현재 pc가 가지고 있는 코드를 실행 한다.
- 파일 입출력 등의 이벤트를 CPU에게 알려주는 것을 '인터럽트 한다' 고 표현
-
선점형 스케쥴러 구현
- 프로세스 running 중에 스케쥴러가 이를 중단시키고, 다른 프로세스로 교체하기 위해, 현재 프로세스 실행을 중단시킴
- 그러려면, 스케쥴러 코드가 실행이 되서, 현 프로세스 실행을 중지시켜야함. = > 인터럽트 기술을 이용
- 프로세스 running 중에 스케쥴러가 이를 중단시키고, 다른 프로세스로 교체하기 위해, 현재 프로세스 실행을 중단시킴
-
IO Device와의 커뮤니케이션
- 저장매체에서 데이터 처리 완료시, 프로세스를 깨워야 함(block state -> ready state)
- 이때 CPU는 다른 프로세스를 처리하고 있는데 , 이 처리 완료 상태를 CPU에게 알림
- IO 관련 처리에는 수많은 인터럽트와 시스템콜, 스케쥴러 등의 동작이 일어난다. 즉, 코드를 짤 때 빈번한 I/O 처리는 엄청난 오버헤드를 발생시킨다. 그래서 왠만하면 한방에 데이터를 읽어서 메모리에 넣고 메모리에서 다 처리할 수 있도록 프로그램 구조를 만드려고 노력하는 것이 중요.
- 그래서 옛날 빅데이터 I/O 처리는 저장 매체에서 액세스하여 엄청난 오버헤드를 발생시켰지만, 요즘은 'spark'라는 플랫폼을 이용하여 모든 데이터를 한 번에 메모리에 올려서 드라마틱하게 처리 속도가 향상되었다.
- 빈번한 I/O 처리가 요즘 이슈가 됨.
-
예외 상황 핸들링
- CPU가 프로그램을 실행하고 있을 때, 입출력 하드웨어 등의 장치나 또는 예외사항이 발생한 경우 CPU가 해당 처리를 할 수 있도록 CPU에 알려줘야함
- 예) 만약 코드중에 '1 나누기 0'을 연산하는 것이 있다면 CPU는 계속 이를 처리하려고 하지만, 실제 0으로 나누는 것은 불가능 하기 때문에 이 코드 때문에 다른 프로세스들이 running 될 수 없다.
- 그러므로 이 프로세스 자체를 kill 시키는데 이 과정에서 인터럽트 기술을 사용한다.
- CPU가 프로그램을 실행하고 있을 때, 입출력 하드웨어 등의 장치나 또는 예외사항이 발생한 경우 CPU가 해당 처리를 할 수 있도록 CPU에 알려줘야함
- 이벤트를 정의하고, 발생시키고 , 그 이벤트를 처리하는 일련의 과정을 운영체제가 맡아서 한다. 그래서 인터럽트라는 기능이 운영체제 안에 구현되어 있다.
-
CPU가 프로그램을 실행하고 있을때,
- 입출력 하드웨어 등의 장치 이슈 발생
- 파일 처리가 끝났다는 것을 운영체제 알려주기
- 운영체제는 해당 프로세스를 block state에서 실행 대기(ready)상태로 프로세스 상태 변경하기
- 입출력 하드웨어 등의 장치 이슈 발생
-
또는 예외사항 발생
- 0으로 나누는 계산이 발생해서, 예외 발생을 운영체제에 알려주기
- 운영체제 가 해당 프로세스 실행 중지/에러 표시를 해서 사용자가 알게끔 해준다.
- 인터럽트는 일종의 이벤트로 불림
- 이벤트에 맞게 운영체제 가 처리
-
1/0 이라는 연산을 하는 코드의 경우 -> 인터럽트 발생 -> 운영체제가 그 프로세스를 죽이고 사용자에게 알린다.(floating point exception(core dumped))
-
타이머 인터럽트
- 선점형 스케쥴러를 위해 필요
- 어느 시점에 강제로 스케쥴러가 CPU가 하는 일을 중단시키고 다른 프로세스를 CPU위에 올려야 하기 때문에 하드웨어로 부터 일정 시간마다 타이머 인터럽트를 운영체제에 알려줌
- 선점형 스케쥴러를 위해 필요
-
입출력 (IO) 인터럽트
- 프린터 , 키보드 , 마우스 , 저장매체(HDD, SSD 등)
- 키보드를 눌렸을때, 마우스를 클릭했을때(이벤트) 등 다양한 이벤트를 처리하는 루틴이 인터럽트이다.
- 프린터 , 키보드 , 마우스 , 저장매체(HDD, SSD 등)
-
내부 인터럽트
- 주로 프로그램 내부에서 잘못된 명령 또는 잘못된 데이터 사용시 발생
- 0으로 나눴을때
- 사용자 모드에서 허용되지 않은 명령 또는 공간 접근시 (c언어의 경우 '포인터'를 사용하여 주소값을 저장하는데 이를 잘못 지칭했을때 발생. (리눅스에서는 하나의 프로세스는 4GB의 공간을 가지는데 3GB는 사용자모드, 1GB는 커널모드인데 사용자의 권한으로 커널모드에 접근했을때 인터럽트가 발생))
- 계산 결과가 Overflow/Underflow 날 때 (c언어의 경우 int는 4byte(=32bit)의 공간을 가지는데 이 중에서도 unsigned 와 signed형으로 나뉜다. unsigned 는 부호가 없는 정수형을 나타내므로 총 2^32 만큼의 숫자를 표현할 수 있고, signed는 부호가 존재하는 정수형을 표현할 수 있으므로 총 2^31 만큼의 숫자를 표현할 수 있다. 이 범위를 초과했을 때 Overflow 인터럽트가 발생한다.)
- 주로 프로그램 내부에서 잘못된 명령 또는 잘못된 데이터 사용시 발생
-
외부 인터럽트
- 주로 하드웨어에서 발생되는 이벤트(프로그램 외부)
- 전원 이상
- 기계 문제
- 키보드등 IO 관련 이벤트
- Timer 관련 이벤트
- 주로 하드웨어에서 발생되는 이벤트(프로그램 외부)
- 내부 인터럽트는 주로 프로그램 내부에서 발생하므로, 소프트웨어 인터럽트라고도 함
- 외부 인터럽트는 주로 하드웨어에서 발생하므로, 하드웨어 인터럽트라고도 함
- 시스템콜 실행을 위해서는 강제로 코드에 인터럽트 명령을 넣어 , CPU에게 실행시켜야 한다.
- 시스템 콜 실제 코드
- eax 레지스터에는 시스템 콜 번호를 넣고, (시스템 콜은 각각 고유의 번호를 갖는다.)
- ebx 레지스터에는 시스템 콜에 해당하는 인자값(파일 입출력의 경우 RDONLY(read only) 등)을 넣고,
- 소프트웨어 인터럽트 명령을 호출하면서 0x80 값을 넘겨줌
mov eax, 1 // 시스템 콜 번호 mov ebx, 0 // 인자 값 전달 int 0x80 // int:CPU에서 제공하는 op code(interrupt) 0x80(인터럽트 번호) 시스템 콜에서 인터럽트의 번호는 0x80
-
시스템 콜 인터럽트 명령을 호출하면서 0x80(int op code)값을 넘겨줌
-
- CPU는 사용자 모드를 커널 모드로 바꿔줌(0x80이라는 op code로 인해 바뀜)
-
- IDT(Interrupt Descriptor Table)에서 0x80에 해당하는 주소(함수)를 찾아서 실행함
- 각 시스템 콜의 번호에는 각각의 코드(주소)가 매핑되어있음.
- 예) %eax(1) : sys_exit , %eax(5) sys_open 등등
-
- system_call() 함수에서 eax로부터 시스템 콜 번호를 찾아서, 해당 번호에 맞는 시스템 콜 함수로 이동
- 유닉스 계열의 경우 system_call()이라는 함수가 c언어로 구현되어있다.
-
- 해당 시스템콜 함수 실행 후, 다시 커널모드에서 사용자 모드로 변경하고, 다시 해당 프로세스 다음 코드 진행
-
-
인터럽트는 미리 정의되어 각각 번호와 실행 코드를 가리키는 주소가 기록되어 있음
- 이벤트(키보드,마우스,파일 입출력 등의 인터럽트) 번호는 IDT(Interrupt Descriptor Table)에 기록되어 있다. (그 때 실행되어야 할 코드의 주소가 각각 매핑되어있다.)
- 컴퓨터 부팅시 운영체제가 기록
- 운영체제 내부 코드 (커널 영역에 존재)
-
다시 예를 보면,
- 항상 인터럽트 발생시, IDT 를 확인
- 시스템콜 인터럽트 명령은 0x80 번호
- 인터럽트 0x80에 해당하는 운영체제 코드는 system_call()이라는 함수
- 즉, IDT에는 0x80 -> system_call()와 같은 정보가 기록되어있음.
- 0~31 : 예외사항 인터럽트(내부/소프트웨어 인터럽트)(일부는 정의 안된채로 남겨져 있음)
- 32~47 : 하드웨어 인터럽트(주변장치 종류/ 갯수에 따라 변경 가능)
- 128 : 시스템 콜 (0x80을 10진수로 하면 128)
-
- 프로세스 실행 중 인터럽트 발생
-
- 현 프로세스 실행 중단
-
- 인터럽트 처리 함수 실행(운영체제)
-
- 현 프로세스 재실행
선점형 스케쥴러 구현 예
- 수시로 타이머 인터럽트 발생
- 타이머 인터럽트도 인터럽트 번호를 가짐 예를 들어 31번
- 운영체제가 타이머 인터럽트 발생 횟수를 기억해서 5번 타이머 인터럽트 발생하면 ,현재 프로세스를 다른 프로세스로 바꿔준다.
- 프로세스의 구성
-
code 영역 : 코드
- code 영역에 컴파일된 소스코드가 저장된다.
-
data : 변수 / 초기화된 데이터
- 선언된 변수가 저장된다.
Ex) int c = 0;
-
stack : 임시 데이터(함수 호출, 로컬 변수 등) (스택 프레임이라고도 부름)
void func(int a,int b) printf(a+b); int c = func(1,2); 라면, 스택 영역에는 a=1,b=2가 들어간다.
- 함수안에서 쓰이는 매개 변수등이 동적으로 저장되고 해제된다.
- 코드영역에서 return address 값을 가지고 스택 영역으로 넘어가며, 스택 영역의 데이터를 다 쓰고 해제될때 가지고온 return address를 통해 다시 코드영역으로 돌아가서 코드를 실행한다.
- 예를 들어 main()함수가 호출되고 main 함수의 매개변수, 지역변수가 처음으로 스택 영역에 쌓이고(return address 값을 가지고 감), 그 main 함수 안에서 다른 함수를 호출 했을 때 역시 변수들이 스택영역에 쌓인다. (자료구조 - stack 의 원리와 같다) 차례로 쌓이고 차례 차례 처리한후 삭제된다.
-
heap : 코드에서 동적으로 만들어지는 데이터
c언어의 malloc() : 동적으로 특정 메모리 공간을 생성
-
SP(stack pointer) + PC(program counter)
- 맨 처음 스택 포인터는 스택 영역의 가장 최상단 부분의 주소를 가리키고 있고, 마찬가지로 프로그램 카운터도 코드 영역의 맨 처음 코드의 주소를 가리키고 있다.
- 스택이 쌓일 때마다 그 top을 스택 포인터가 가리킨다. 프로그램 카운터도 스택 포인터(sp)와 마찬가지로 코드영역에서 실행되고 있는 해당 라인의 주소값을 가지고 있다.
-
EBP 레지스터
- 함수가 함수를 호출하고 그 함수가 또 함수를 호출하다 문제가 생기면 어느 function에서 문제가 생겼는지 모르는 경우가 생긴다. 그래서 EBP 레지스터는 다음 함수가 호출될 때 이전에 스택 포인터가 가르키는 주소값을 가지고 있다가 , 문제가 생겼을 때 어느 부분에서 문제가 생겼는지 빠르게 tracking 하는 용도로 사용된다.
-
EAX 레지스터
- 이 때 함수를 호출하고 난 후의 결과값을 EAX에 저장한다.
int c = func(1,2) 라면 EAX는 3이라는 결과값을 가지고 있다.
- 이 때 함수를 호출하고 난 후의 결과값을 EAX에 저장한다.
-
c언어는 컴파일 언어이므로 코드들이 한 번에 코드 영역으로 올라가서 컴파일 되어 실행 파일이 생기지만 , 파이썬 등의 인터프리터 언어는 번역과 실행이 동시에 이뤄진다.
#include <stdio.h>
#include <stdlib.h>
int main(){
int * data;
data = (int * ) malloc(sizeof(int)); //동적으로 메모리를 생성하는 함수. 해당 라인은 32bit의 메모리크기를 할당받음. 실제 heap 공간에 적재되는 라인 // free(data) 해서 메모리를 해제시키면 heap영역에서 없어짐
* data = 1;
printf("%d\n",* data);
return 0;
}
파이썬 같은 언어의 경우 신경쓸 필요가 없다!
- main함수가 실행되면 stack 영역에 Return address(RET)를 할당한다.
- 결국 stack 영역에 적재된 * data 는 heap 영역에 할당받은 메모리 공간의 주소를 가진다. (Ex. data = 1000h)
- heap 영역에는 * data = 1; 이라고 정의했으므로 해당 data가 가리키는 주소에는 '1' 이라는 값을 가진다.
- 처음에 컴파일러에서 정의되어있는 _start()가 실행되고 그 안에 main()이라는 함수가 들어있는데, 그 안에서 우리가 만든 c프로그램이 실행되고, 다 실행된 후에 '프로세스 종료 처리'가 발생하고 끝난다.
DATA 영역을 BSS 와 DATA로 분리
출처 : http://www.drdobbs.com/security/anatomy-of-a-stack-smashing-attack-and-h/240001832
#include <stdio.h>
#include <stdlib.h>
int global_data1; // 초기화 되지 않은 전역변수
int global_data2 = 1; //초기화 된 전역 변수
int main(){
int * data; // main 함수 안에서 선언된 지역 변수 : stack 영역에 올라감
data = (int * ) malloc(sizeof(int))
* data = 1;
printf("%d\n",* data);
return 0;
}
- BSS : 초기화 되지 않은 전역 변수
- DATA : 초기값이 있는 전역 변수
-
컨텍스트 스위칭 : 멀티프로세스 환경에서 CPU가 어떤 하나의 프로세스를 실행하고 있는 상태에서 인터럽트 요청에 의해 다음 우선 순위의 프로세스가 실행되어야 할 때 기존의 프로세스의 상태 또는 레지스터 값(Context)을 저장하고 CPU가 다음 프로세스를 수행하도록 새로운 프로세스의 상태 또는 레지스터 값(Context)를 교체하는 작업을 Context Switch(Context Switching)라고 한다.
-
PC(Program Counter) + SP(Stack Pointer) 와 프로세스로 컨텍스트 스위칭을 가능하게 만든다.
-
OS에서 Context는 CPU가 해당 프로세스를 실행하기 위한 프로세스의 정보(PC,SP 등)들인데, 이 Context는 각각 프로세스의 PCB(Process Control Block)에 저장된다.
-
그래서 Context Switching 때 PCB의 정보를 읽어(적재) CPU가 전에 프로세스가 일을 하던거에 이어서 수행이 가능한 것이다.
-
- process A 의 PC : 0002h SP : EFFEh
-
- process B 의 PC : 0004h SP : EFFEh
- 이 정보(context)를 PCB에 저장해두고 A를 ready 상태로 만들고 B를 running 시킨다.
-
-
PCB의 저장정보
- process ID
- Register 값 (PC,SP 등)
- Scheduling info (process State) : ready, running, block
- Memory info (메모리 사이즈)
PCB는 프로세스가 실행중인 상태를 캡쳐/구조화 해서 저장
- CPU에 실행할 프로세스를 교체하는 기술 (이것도 역시 코드로 이루어져 있음)
- 실행 중지할 프로세스 정보를 해당 프로세스의 PCB에 업데이트해서, 메인 메모리에 저장
- 다음 실행할 프로세스 정보를 메인 메모리에 있는 해당 PCB 정보(PC,SP 등)를 CPU의 레지스터에 넣고, 실행
디스패치(dispatch) : ready 상태의 프로세스를 running 상태로 바꾸는 것 Context Switching 때 해당 CPU는 아무런 일을 하지 못한다. 따라서 컨텍스트 스위칭이 잦아지면 오히려 오버헤드가 발생해 효율(성능)이 떨어진다. 그래서 PC,SP에 메모리 주소를 쓰는 과정은 instruction이 적은 어셈블리어로 작성되는 경우가 많다. Context Switching도 결국은 코드로 이루어져있으므로 프로세스 사이에 그 코드가 실행되는 것이므로 결국은 오버헤드라고 볼 수 있음.
- 컴파일러
- 초기 컴퓨터 프로그램들은 어셈블리어로 작성
- 서로 다른 CPU 아키텍처가 등장할 때마다 매번 똑같은 프로그램 작성
- 어셈블리어로는 프로그램 작성 속도가 매우 떨어짐(메모리 주소를 일일이 할당해주고 많은 명령어가 들어감)
- 그러므로 속도는 빠르지만 이식성이 떨어짐
- 컴파일러 등장
- CPU 아키텍처에 따라서는 컴파일러 프로그램만 만들면 됨, 기존 코드를 재작성할 필요 없음 (이식성이 높음)
- 그러나 , 어셈블리어로 작성한 코드보다는 속도가 떨어질 수 있음.
- 리눅스의 경우 많은 CPU 아키텍처를 지원하는데, 각 CPU 별로 컨텍스트 스위칭 코드(어셈블리어로 작성된)는 별도로 존재한다!!!
- 초기 컴퓨터 프로그램들은 어셈블리어로 작성
- 원칙적으로 프로세스는 다른 프로세스의 공간을 접근할 수 없다.
프로세스들이 서로의 공간을 쉽게 접근할 수 있다면 ? ==> 프로세스 데이터/코드가 바뀔 수 있으니 매우 위험!
-
프로세스간 통신이 왜 필요할까?
- 성능을 높이기 위해 여러 프로세스를 만들어서 동시 실행 ( 요즘의 CPU는 코어가 8개 부터 16개 등 다양한데, 성능을 높이기 위해 각 코어에서 프로세스 처리를 분담하면서 서로 간의 커뮤니케이션이 필요하게 된다.)
- 이 때 프로세스간 상태 확인 및 데이터 송수신이 필요
-
프로세스간에 커뮤니케이션이 필요할 때는 IPC방식을 이용한다.
-
fork() 시스템 콜
- fork() 함수로 프로세스 자신을 복사해서 새로운 프로세스로 만들 수 있음
- 부모 프로세스, 자식 프로세스
- 프로세스를 fork()해서, 여러 프로세스를 동시에 실행시킬 수 있음
CPU의 코어가 8개라고 했을 때 8초에 처리할 각 프로세스를 각 코어에서 동시에 실행함으로써 1초만에 실행가능 (병렬 처리)
- Ex . 1~10000까지 더하기 (코어가 10개라고 가정)
- fork()함수로 10개 프로세스를 만들어서 , 각각 1
1000, 10012000,...,9001~10000 더하기 - 각각 더한 값을 모두 합하면, 더 빠르게 동작 가능
단, 이때 각 프로세스가 더한 값을 수집해야하므로, 프로세스간 통신 필요
- fork()함수로 10개 프로세스를 만들어서 , 각각 1
- fork() 함수로 프로세스 자신을 복사해서 새로운 프로세스로 만들 수 있음
-
웹 서버 예시
- 웹서버란 요청이 오면 HTML 파일을 클라이언트에 제공하는 프로그램
- 새로운 사용자 요청이 올 때마다, fork() 함수로 새로운 프로세스 만들고, 각 사용자 요청에 즉시 대응
- 프로세스간에 직접적으로 커뮤니케이션은 불가능하지만, 공유하는 저장매체를 통해서는 가능하다.
- 간단히 다른 프로세스에 전달할 내용을 파일에 쓰고, 다른 프로세스가 해당 파일을 읽으면 된다.
- 파일을 이용한 방법도 IPC 방법의 일종
- file을 사용하면, 실시간으로 직접 원하는 프로세스에 데이터 전달이 어려움
- 해당 프로세스가 파일을 읽어야 하는데, 실시간으로 읽고만 있을 수는 없기 때문
그래서 보다 다양한 IPC 기법이 있다.
-
가상 메모리란 물리적으로 존재 하지 않는 메모리를 말하며, OS가 만들어낸 논리적인 형태의 메모리를 말한다. 가상 메모리에 저장된 데이터라고 해서 가상으로 저장 되는 것이아니라 실제로는 물리메모리나 하드 디스크에 저장된다. 가상 메모리는 VAS라는 것을 OS로부터 할당 받는다.
VAS(Virtual Address Space) : 가상 메모리에 할당되는 주소를 말하며 물리메모리 주소와 다른 주소이다.
- 가상메모리안에 꽉 차있는 것이아니라 '코드 영역, 데이터영역, 스택영역 등등'은 매우 작은 공간에 불과하며, 이외에도 많은 영역이 존재한다.
- 사용자 모드에서는 커널 공간 접근 불가
- 32bit 시스템의 경우 4GB 크기의 가상 메모리 영역을 할당 받고, 64bit 시스템의 경우 이론적으로 17EB의 가상 메모리 영역을 할당 받지만 그 영역의 크기가 너무 크고 비효율 적이여서 8TB정도로 제한을 하고 있다. 가상 메모리의 크기는 시스템의 bit수에 따라 정해져 있고 무조건적으로 정해진 크기가 할당된다.
- 커널 공간은 공유한다.
- 프로세스 10개를 올릴 때 운영체제 커널 영역 공간은 실제 물리 메모리에 들어갈 때는 동일한 공간을 공유한다. (같은 커널인데 여러개를 올리는 것은 낭비이므로)
내 PC가 8GB라면 프로세스를 두개 밖에 올리지 못할까? 당연히 그건 아니다. 프로세스의 주소 공간(4GB)은 다 쓰는게 아니라 일부분만 사용한다. 공간 중 극히 일부분만 실제 물리 메모리에 올라가기때문에 큰 이슈가 없다.
- file 사용 : 저장매체를 사용하는 것, 실시간으로 체크해야하므로 비효율적이다
- Message Queue
- Shared Memory
- Pipe
- Signal
- Semaphore
- Socket
두번째부터는 모두 커널 공간을 사용하는 것 ( 커널 공간은 물리 메모리에 있으므로 저장매체보다 액세스 시간이 짧고, 프로세스가 공유할 수 있다 )
- 여러 프로세스 동시 실행을 통한 성능 개선, 복잡한 프로그램을 위해 프로세스간 통신 필요
- 프로세스간 공간이 완전 분리
- 프로세스간 통신을 위한 특별한 기법
- IPC(InterProcess Communication)
- 대부분 IPC 기법은 결국 커널 공간을 활용하는 것이다.
- WHY? 커널 공간은 프로세스간에 공유하기 때문에
- 기본 파이프는 단방향 통신 (부모 -> 자식)
- 부모 프로세스는 fd[1]으로 write로 쓰기만 하고, 자식 프로세스는 fd[0]로 읽기만 한다.
- 부모 프로세스에서 fd[0]으로 read는 불가능하고 , 자식 프로세스에서 fd[1]로 write하는 것은 불가능하다.
- fork()로 자식 프로세스를 만들었을 때, 부모와 자식간의 통신
출처 : https://medium.com/@ramyjzh/data-structures-for-dummies-stacks-queues-5785694cd87f
- 메세지 큐 예제
- A프로세스
msqid = msgget(key,msgflg) //key는 1234,msgflg는 옵션 msgsnd(msqid,&sbuf,buf_length,IPC_NOWAIT)
- B프로세스
msqid = msgget(key,msgflg) //key는 동일하게 1234로 해야 해당 큐의 msgid를 얻을 수 있음 msgrcv(msqid,&rbuf,MSGSZ,1,0)
보통은 ftok()를 이용하여 pathname(존재하고 접근 가능한 파일 혹은 디렉토리)과 proj_id값의 조합으로 IPC에서 사용할 key값을 얻는다. key_t key = ftok(const char * path, int id);
- message queue는 부모/자식이 아니라(fork()할 필요가 없다), 어느 프로세스간에라도 데이터 송수신이 가능
- 먼저 넣은 데이터가, 먼저 읽혀진다.
- 부모/자식 프로세스간 only or not
- 단방향만 가능 or 양방향 가능
pipe, message queue 모두 프로세스간 데이터 송수신을 위해 kernel 공간의 메모리를 사용한다.
출처 : https://static.ernw.de/whitepaper/ERNW_Newsletter_54_Xenpwn_v.1.0_signed.pdf
- 노골적으로 kernel space에 메모리 공간을 만들고, 해당 공간을 변수처럼 쓰는 방식
- message queue 처럼 FIFO 방식이 아니라, 해당 메모리 주소를 마치 변수처럼 접근하는 방식
- 공유 메모리 key를 가지고, 여러 프로세스가 접근가능
-
- 공유 메모리 생성 및 공유 메모리 주소 얻기
shmid = shmget((key_t)1234,SIZE,IPC_CREAT |0666); // 공유 메모리 생성: int shmget(key_t key, size_t size, int shmflg);
shmaddr = shmat(shmid,(void *)0,0) // 공유 메모리 연결: void *shmat(int shmid, const void *shmaddr, int shmflg);
-
- 공유 메모리에 쓰기
strcpy((char *)shmaddr,"Linux programming"); //메모리의 주소에 바로 해당 문자열을 쓴다.
-
- 공유 메모리에서 읽기
printf("%s\n",(char *)shmaddr); //메모리의 주소에 있는 문자열을 바로 읽어들인다.
-
- 공유 메모리 해제
int shmdt(char *shmaddr);
-
- 공유 메모리 제어
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
IPC 기법이지만, 이외에도 많이 사용되는 두 가지 기술
- 시그널 (signal)
- 소켓 (socket)
- 유닉스에서 30년 이상 사용된 전통적인 기법
- 커널 또는 프로세스에서 다른 프로세스에 어떤 이벤트가 발생되었는 지를 알려주는 기법
- 프로세스 관련 코드에 관련 시그널 핸들러를 등록해서, 해당 시그널 처리 실행
- 시그널 무시
- 시그널 블록(블록을 푸는 순간, 프로세스에 해당 시그널 전달)
- 등록된 시그널 핸들러로 특정 동작 수행
- 등록된 시그널 핸들러가 없다면, 커널에서 기본 동작 수행
주요 시그널: 기본 동작
- SIGKILL : 프로세스를 죽여라 (슈퍼관리자가 사용하는 시그널로, 프로세스는 어떤 경우든 죽도록 되어 있음)
- SIGALARM : 알람을 발생한다.
- SIGSTP : 프로세스를 멈춰라(Ctrl + z)
- SIGCONT : 멈춰진 프로세스를 실행해라
- SIGINT : 프로세스에 인터럽트를 보내서 프로세스를 죽여라(Ctrl + c)
- SIGSEGV : 프로세스가 다른 메모리 영역을 침범했다.
- SIGUSR1 , SIGUSR2 : 기본 동작이 정의되어있지 않고 프로그램으로 프로세스를 만들때 특정 signal이 특별 동작을 취하도록 정의할 수 있다.
- SIGINT는 원래 프로세스를 죽이는 시그널이지만 , SIGUSR1을 이용하여 다른 특별 동작을 행하도록 정의할 수 있다.
시그널 종류 : kill -l (리눅스 터미널에서 쳐보면 종류를 알 수 있다.)
- PCB에 해당 프로세스가 블록 또는 처리해야하는 시그널 관련 정보 관리
- process ID
- Register 값 (PC,SP 등)
- Scheduling info (process State) : ready, running, block
- Memory info (메모리 사이즈)
- 처리할 시그널 관련 정보
프로세스가 동작할 때 사용자 모드(3GB) 커널 모드(1GB) 사이에 엄청 나게 많은 스위칭이 일어난다. 저장 매체로의 접근이나 시스템 콜, 인터럽트 등이 발생했을 때 커널 모드로 전환이 되는데(Protection ring 부분 참고), 사용자 모드에서 커널 모드로 전환해서 그 task를 처리한후 사용자 모드로 전환되는 시점에 해당 프로세스의 PCB signal 자료구조를 확인하여 해당 프로세스에 대한 처리가 필요하다면 처리를 담당하는 커널 함수를 실행한 후에 사용자 모드로 돌아간다. 즉, 커널 모드 -> 사용자 모드 전환시 시그널 처리가 발생한다.
- 소켓은 네트워크 통신을 위한 기술
- 기본적으로는 클라이언트와 서버등 두 개의 다른 컴퓨터간의 네트워크 기반 통신을 위한 기술
- 또 하나의 컴퓨터 안에서, 두 개의 프로세스간에 통신 기법(IPC)으로 사용 가능
출처 :http://www.drdobbs.com/security/anatomy-of-a-stack-smashing-attack-and-h/240001832
시스템 소프트웨어에는 API,플랫폼,쉘,컴파일러 등이 있다. 그런 것들을 개발하는 직종을 '시스템 프로그래머'라고 한다.
- Light Weight Process 라고도 함
- 프로세스
- 프로세스 간에는 각 프로세스의 데이터 접근이 불가 (병렬 처리를 위해 IPC를 꼭 써야함)
- 스레드
- 하나의 프로세스에 여러개의 스레드 생성 가능
- 스레드들은 동시에 실행 가능
- 프로세스 안에 있으므로, 프로세스의 데이터를 모두 접근 가능 (가장 큰 장점: IPC를 사용할 필요가 없다.)
- Thread는 하나의 프로세스 안의 stack 영역에 존재하는 하나의 함수로 보면 됨.
- process에서 thread를 생성하면 stack영역에 각 thread를 위한 영역이 할당됨.
- 각 thread를 위한 PC(program counter),SP(stack pointer)가 존재
- thread는 상태 정보를 가지는 레지스터와 스택 영역을 가지고 있고 그 프로세스의 HEAP,DATA,CODE영역은 공유한다.
- 그래서 thread는 HEAP,DATA(BSS,DATA),CODE 영역은 공유한다.
- 즉, thread 끼리는 별도의 IPC 방법 없이 서로 데이터 공유가 가능하다. (단, 다른 프로세스의 thread와의 데이터 공유는 불가능)
출처 : https://stackoverflow.com/questions/33221759/c-thread-function-clarification
- 멀티 프로세싱과 Thread
-
- 사용자에 대한 응답성 향상 (웹 서버 같은 경우, 클라이언트가 html을 요청했을때 thread를 이용하여 클라이언트의 요청을 처리해줌)
-
- 자원 공유 효율
- IPC기법과 같이 프로세스간 자원 공유를 위해 번거로운 작업이 필요없음
- 프로세스 안에 있으므로, 프로세스의 데이터를 모두 접근가능
- 프로세스는 4GB를 사용, 3개가 있으면 총 12GB를 사용하게 될 수도 있음. 스레드는 프로세스 안에 존재하고 영역을 공유하기 때문에 스레드 3개가 생성되더라도 4GB그대로 유지
-
- 작업이 분리되어 코드가 간결
- 스레드중 한 스레드만 문제가 있어도, 전체 프로세스가 영향을 받음
- 스레드를 많이 생성하면, Context Switching이 많이 일어나, 성능 저하
- Ex ) 리눅스 OS에서는 Thread를 Process와 같이 다룸
- 스레드를 많이 생성하면, 모든 스레드를 스케쥴링해야 하므로, Context Switching이 빈번할 수 밖에 없음
- Ex ) 리눅스 OS에서는 Thread를 Process와 같이 다룸
- 프로세스는 독립적,스레드는 프로세스의 서브셋
- 프로세스는 각각 독립적인 자원을 가짐, 스레드는 프로세스 자원 공유
- 프로세스는 자신만의 주소영역을 가짐, 스레드는 주소영역 공유
- 프로세스간에는 IPC기법으로 통신해야함, 스레드는 필요 없음
- POSIX 스레드(POSIX Threads, 약어: PThread)
- Thread 관련 표준 API (c언어 단에서)
- Thread 개념 정리
- 프로세스와 달리 스레드간 자원 공유
- 스레드 장점
- CPU 활용도를 높임
- 성능 개선 기능
- 응답성 향상
- 자원 공유 효율(IPC를 안써도 됨)
- 스레드 단점
- 하나의 스레드 문제가, 프로세스 전반에 영향을 미침
- 여러 스레드 생성시 성능 저하 기능
- 스레드 A,B,C가 있을 때 영역을 공유하기 때문에 각 스레드가 변수의 내용을 바꾸었을 때, 다른 스레드가 그 변수를 읽었을 때 문제가 발생할 수 있다.
- 그래서 스레드 관리가 필요함
import threading
g_count = 0 # 전역변수 선언
def thread_main():
global g_count # g_count란 전역 변수를 사용하겠다는 의미
for i in range(10000):
g_count = g_count + 1
threads = []
for i in range(50):
th = threading.Thread(target = thread_main)
threads.append(th)
for th in threads:
th.start()
for th in threads:
th.join() # 다른 스레드가 다 끝날때 까지 기다려주는 함수, 다 끝나면 내려가서 print 함수 호출 ===> 동기화(기간을 맞춘다)
print('g_count = ',g_count)
# 다음의 경우 문제가 된다. (반목문 횟수를 1000000 이상 증가할 경우, Thread가 온전히 100000번 반복되지 않고, 다른 Thread로 Context Switching)
# 이때 어느 부분을 실행하다가 Context switching 될지는 알 수 없으므로, 게산상의 오류가 발생
# Context switching시에 값을 저장하고 있던 register 값은 실행중단 이전의 값으로 모두 복원된다.
# 이미 스레드가 g_count 값을 저장하고 난 후에 컨텍스트 스위칭이 되었다면, 다시 스레드가 실행할 때 최신화된 g_count가 아닌 이전의 g_count 값을 가지고 있으므로 덧셈이 누락된다.
import threading
g_count = 0
def thread_main():
global g_count
lock.acquire() # 임계영역을 정해서 한 번에 한 스레드만 이 코드를 사용할수 있도록 한다. (열쇠라고 생각하면, 중간에 Context switching이 되어 다른 스레드가 이 영역을 요청해도 하나의 스레드가 아직 열쇠를 가지고 있으므로 다른 스레드는 이 코드를 실행할 수 없음 )
for i in range(1000000):
g_count = g_count + 1
lock.release() # 임계영역 해제 ( 열쇠 반환 )
threads = []
lock = threading.Lock()
for i in range(50):
th = threading.Thread(target = thread_main)
threads.append(th)
for th in threads:
th.start()
for th in threads:
th.join()
print('g_count = ',g_count)
# mutual exclusion 사용