Biz Assist에 외부 공고 수집처를 추가하면서 BidCandidateCollector 인터페이스를 만들었다. public interface BidCandidateCollector { List<BidQualificationDto> collect( LocalDate startDate, LocalDate endDate ); } interface가 뭐였지 인터페이스는 클래스가 어떤 기능을 가져야 하는지 정하는 약속 이다. 위 코드에서는 collect(startDate, endDate) 라는 메서드를 구현해야 한다. 즉 수집처가 어디든 기간을 받아서 공고 후보 목록을 반환해야 한다. implements 인터페이스를 실제 클래스에서 구현할 때는 implements 를 사용한다. class KoreaExpresswayBidCollector implements BidCandidateCollector { } 그리고 인터페이스에 정의된 메서드를 실제로 구현한다. @Override public List<BidQualificationDto> collect( LocalDate startDate, LocalDate endDate ) { // 수집 로직 } Biz Assist에서는 왜 썼나 나라장터와 한국도로공사는 공고를 가져오는 방식이 다르다. 그래도 Biz Assist 입장에서는 둘 다 공고 후보를 가져오는 수집기 다. 그래서 실제 구현은 각 클래스에서 다르게 하고 밖에서는 같은 BidCandidateCollector 타입으로 다룰 수 있게 했다. 나라장터 수집기 한국도로공사 수집기 다른 수집처 ↓ 모두 BidCandidateCollector 다시 정리 interface → 해야 할 기능의 형태 정의 implements → 인터페이스를 실제 클래스에서 구현 @Override → 정의된 메서드를 실제 동작으로 작성 이번에 직접 써보니까 interface는 구현 방식은 다르지만 같은 역할을 하는 클래스들을 묶는 기준 이라고 생각하면 이해하기 쉬웠다.
네이버 영화 리뷰 데이터에 대한 이해와 전처리 LSTM을 이용한 네이버 영화 리뷰 분류 모델 평가 코드 작성 학습 모델 로드 및 평가 모델 테스트 <딥러닝 파이토치 교과서 - 입문부터 LLM 파인튜닝까지> 교재 13-2장의 내용을 공부하고 정리한 포스트입니다. https://wikidocs.net/book/2788 - 교재 https://github.com/ukairia777/pytorch-nlp-tutorial - 깃허브 이번 주에 배운 걸 전부 합쳐보자. 4.3의 형태소 토큰화로 리뷰를 나누고, 정수 인코딩한 뒤 4.4의 nn.Embedding() 으로 벡터로 바꾸고, 4.2의 LSTM에 넣어 긍정/부정을 맞히는 모델을 만든다. 4.1에서 본 다 대 일 (many-to-one) 구조다. 데이터는 네이버 영화 리뷰 데이터다. 총 200,000개 리뷰로, 리뷰 텍스트와 긍정이면 1, 부정이면 0인 레이블로 구성돼 있다. 1. 네이버 영화 리뷰 데이터에 대한 이해와 전처리 데이터 다운로드 링크 : https://github.com/e9t/nsmc/ import pickle import pandas as pd import numpy as np import matplotlib.pyplot as plt import re import urllib.request from konlpy.tag import Mecab from tqdm import tqdm from sklearn.model_selection import train_test_split from collections import Counter 1) 데이터 로드하기 훈련 데이터 ratings_train.txt와 테스트 데이터 ratings_test.txt를 다운로드한다. urllib.request.urlretrieve("https://raw.githubusercontent.com/e9t/nsmc/master/ratings_train.txt", filename="ratings_train.txt") urllib.request.urlretrieve("https://raw.githubusercontent.com/e9t/nsmc/master/ratings_test.txt", filename="ratings_test.txt") pandas로 훈련 데이터는 train_data 에, 테스트 데이터는 test_data 에 저장한다. train_data = pd.read_table('ratings_train.txt') test_data = pd.read_table('ratings_test.txt') print('훈련용 리뷰 개수 :',len(train_data)) # 훈련용 리뷰 개수 출력 훈련용 리뷰 개수 : 150000 훈련 데이터는 150,000개다. 상위 5개를 출력해보자. train_data[:5] # 상위 5개 출력 id, document, label 3개 열로 구성돼 있다. id는 감성 분류에 도움이 안 되니 무시한다. 결국 모델은 리뷰 내용인 document와 긍정(1)/부정(0)인 label 두 열을 학습해야 한다. 상위 5개만 봐도 한국어 데이터의 특징이 보인다. 2번 샘플은 4.3에서 본 것처럼 띄어쓰기를 안 해도 이해되는 한국어 특성 때문에 띄어쓰기가 안 돼 있다. 테스트 데이터도 확인하자. print('테스트용 리뷰 개수 :',len(test_data)) # 테스트용 리뷰 개수 출력 테스트용 리뷰 개수 : 50000 test_data[:5] 테스트 데이터는 50,000개이고 형식은 훈련 데이터와 같다. 2) 데이터 정제하기 훈련 데이터에 중복이 있는지 확인한다. # document 열과 label 열의 중복을 제외한 값의 개수 train_data['document'].nunique(), train_data['label'].nunique() (146182, 2) 150,000개 중 document 열의 고유값이 146,182개라는 건 약 4,000개가 중복이라는 뜻이다. label은 0 또는 1이라 2가 나온다. 중복을 제거하자. # document 열의 중복 제거 train_data.drop_duplicates(subset=['document'], inplace=True) print('총 샘플의 수 :',len(train_data)) 총 샘플의 수 : 146183 146,182가 아니라 146,183인 이유는 nunique() 가 Null을 세지 않기 때문이다. 아래에서 보는 Null 샘플 1개가 남아 있다. 레이블 분포를 보자. train_data['label'].value_counts().plot(kind = 'bar') 긍정과 부정 모두 약 72,000개로 분포가 균일해 보인다. 정확한 개수를 확인하자. print(train_data.groupby('label').size().reset_index(name = 'count')) label count 0 0 73342 1 1 72841 레이블 0인 리뷰가 근소하게 많다. Null 값을 가진 샘플이 있는지 확인한다. print(train_data.isnull().values.any()) True Null이 있다. 어느 열에 있는지 보자. print(train_data.isnull().sum()) id 0 document 1 label 0 dtype: int64 document 열에 1개 있다. 어떤 샘플인지 출력해보자. train_data.loc[train_data.document.isnull()] Null 샘플을 제거한다. train_data = train_data.dropna(how = 'any') # Null 값이 존재하는 행 제거 print(train_data.isnull().values.any()) # Null 값이 존재하는지 확인 False print(len(train_data)) 146182 1개가 제거됐다. 이제 온점(.)이나 ? 같은 특수문자를 지우고 한글만 남기기 위해 정규 표현식을 쓴다. 영어로 먼저 보자. 알파벳을 나타내는 정규 표현식은 [a-zA-Z] 다. 이를 응용하면 알파벳과 공백을 제외하고 모두 지울 수 있다. #알파벳과 공백을 제외하고 모두 제거 eng_text = 'do!!! you expect... people~ to~ read~ the FAQ, etc. and actually accept hard~! atheism?@@' print(re.sub(r'[^a-zA-Z ]', '', eng_text)) 'do you expect people to read the FAQ etc and actually accept hard atheism' 한국어도 같은 원리다. 한글 범위만 정하면 된다. 자음은 ᄀ ~ ᄒ, 모음은 ᅡ ~ ᅵ로 지정한다. 자모 참고 : https://www.unicode.org/charts/PDF/U3130.pdf ᄀ ~ ᄒ : 3131 ~ 314E ᅡ ~ ᅵ : 314F ~ 3163 완성형 한글은 가 ~ 힣으로 지정한다. 완성형 참고 : https://www.unicode.org/charts/PDF/UAC00.pdf 이 범위를 모두 반영해 한글과 공백을 제외하고 전부 제거하자. # 한글과 공백을 제외하고 모두 제거 train_data['document'] = train_data['document'].str.replace("[^ᄀ-하-ᅵ가-힣 ]","", regex=True) train_data[:5] 띄어쓰기는 유지되고 구두점은 제거됐다. 그런데 네이버 영화 리뷰는 영어, 숫자, 특수문자로만 쓸 수도 있다. 한글이 없던 리뷰는 이제 빈(empty) 값이 됐을 것이다. 공백만 있거나 빈 값인 행을 Null로 바꾸고 확인하자. train_data['document'] = train_data['document'].str.replace('^ +', "", regex=True) # white space 데이터를 empty value로 변경 train_data['document'].replace('', np.nan, inplace=True) print(train_data.isnull().sum()) id 0 document 789 label 0 dtype: int64 Null이 789개나 새로 생겼다. 5개만 보자. train_data.loc[train_data.document.isnull()][:5] 레이블은 긍정일 수도 부정일 수도 있지만, 텍스트가 없으니 의미 없는 데이터다. 제거한다. train_data = train_data.dropna(how = 'any') print(len(train_data)) 145393 145,393개가 남았다. 테스트 데이터에도 같은 전처리를 한다. test_data.drop_duplicates(subset = ['document'], inplace=True) # document 열에서 중복인 내용이 있다면 중복 제거 test_data['document'] = test_data['document'].str.replace("[^ᄀ-하-ᅵ가-힣 ]","", regex=True) # 정규 표현식 수행 test_data['document'] = test_data['document'].str.replace('^ +', "", regex=True) # 공백은 empty 값으로 변경 test_data['document'].replace('', np.nan, inplace=True) # 공백은 Null 값으로 변경 test_data = test_data.dropna(how='any') # Null 값 제거 print('전처리 후 테스트용 샘플의 개수 :',len(test_data)) 전처리 후 테스트용 샘플의 개수 : 48852 3) 토큰화 토큰화하면서 불용어 (stopwords)도 제거한다. 불용어는 정의하기 나름이다. 조사, 접속사 같은 보편적인 불용어를 쓸 수도 있지만, 결국 데이터를 계속 검토하면서 추가해가는 경우가 많다. 현업이라면 보통 아래보다 더 많은 불용어를 쓴다. stopwords = ['도', '는', '다', '의', '가', '이', '은', '한', '에', '하', '고', '을', '를', '인', '듯', '과', '와', '네', '들', '듯', '지', '임', '게'] 형태소 분석기는 KoNLPy의 Mecab을 쓴다. mecab = Mecab() mecab.morphs('와 이런 것도 영화라고 차라리 뮤직비디오를 만드는 게 나을 뻔') ['와', '이런', '것', '도', '영화', '라고', '차라리', '뮤직', '비디오', '를', '만드', '는', '게', '나을', '뻔'] 4.3에서 본 것처럼 한국어는 띄어쓰기가 아니라 형태소 분석기로 토큰화한다. 훈련 데이터를 토큰화하면서 불용어를 제거해 X_train 에 저장한다. X_train = [] for sentence in tqdm(train_data['document']): tokenized_sentence = mecab.morphs(sentence) # 토큰화 stopwords_removed_sentence = [word for word in tokenized_sentence if not word in stopwords] # 불용어 제거 X_train.append(stopwords_removed_sentence) print(X_train[:3]) [['아', '더', '빙', '진짜', '짜증', '나', '네요', '목소리'], ['흠', '포스터', '보고', '초딩', '영화', '줄', '오버', '연기', '조차', '가볍', '않', '구나'], ['너무', '재', '밓었다그래서보는것을추천한다']] 세 번째 샘플처럼 띄어쓰기가 안 된 데다 오타('밓었다')까지 있으면 형태소 분석기도 제대로 못 나눈다. 테스트 데이터도 똑같이 토큰화한다. X_test = [] for sentence in tqdm(test_data['document']): tokenized_sentence = mecab.morphs(sentence) # 토큰화 stopwords_removed_sentence = [word for word in tokenized_sentence if not word in stopwords] # 불용어 제거 X_test.append(stopwords_removed_sentence) 4) 학습 데이터, 검증 데이터, 테스트 데이터 학습 중 성능을 평가할 검증 데이터 가 추가로 필요하다. 레이블 열을 따로 분리해 y_train , y_test 로 저장한다. 그러면 학습 데이터는 X_train , y_train , 테스트 데이터는 X_test , y_test 가 된다. 학습 데이터의 20%를 떼어 검증 데이터를 만든다. 데이터 분리는 주로 사이킷런의 train_test_split 을 쓰고, test_size 에 비율을 넣으면 그만큼 분할해준다. 랜덤 분할 중 레이블 불균형이 생기지 않도록 레이블 비율을 유지하고 싶다면 stratify 에 y 데이터를 넣는다. y_train = np.array(train_data['label']) y_test = np.array(test_data['label']) X_train, X_valid, y_train, y_valid = train_test_split(X_train, y_train, test_size=0.2, random_state=0, stratify=y_train) 비율이 잘 유지됐는지 확인하자. print('--------학습 데이터의 비율-----------') print(f'부정 리뷰 = {round(np.sum(y_train==0)/len(y_train) * 100,3)}%') print(f'긍정 리뷰 = {round(np.count_nonzero(y_train)/len(y_train) * 100,3)}%') print('--------검증 데이터의 비율-----------') print(f'부정 리뷰 = {round(np.sum(y_valid==0)/len(y_valid) * 100,3)}%') print(f'긍정 리뷰 = {round(np.count_nonzero(y_valid)/len(y_valid) * 100,3)}%') print('--------테스트 데이터의 비율-----------') print(f'부정 리뷰 = {round(np.sum(y_test==0)/len(y_test) * 100,3)}%') print(f'긍정 리뷰 = {round(np.count_nonzero(y_test)/len(y_test) * 100,3)}%') --------학습 데이터의 비율----------- 부정 리뷰 = 50.238% 긍정 리뷰 = 49.762% --------검증 데이터의 비율----------- 부정 리뷰 = 50.239% 긍정 리뷰 = 49.761% --------테스트 데이터의 비율----------- 부정 리뷰 = 49.808% 긍정 리뷰 = 50.192% 학습 데이터와 검증 데이터의 레이블 비율이 같다. 5) 단어 집합 만들기 텍스트를 숫자로 처리하려면 정수 인코딩을 해야 한다. 먼저 훈련 데이터로 단어 집합 (vocabulary)을 만든다. word_list = [] for sent in X_train: for word in sent: word_list.append(word) word_counts = Counter(word_list) print('총 단어수 :', len(word_counts)) 총 단어수 : 45296 단어가 45,000개가 넘는다. Counter() 로 셌기 때문에 각 단어의 등장 빈도가 저장돼 있다. print('훈련 데이터에서의 단어 영화의 등장 횟수 :', word_counts['영화']) print('훈련 데이터에서의 단어 공감의 등장 횟수 :', word_counts['공감']) 훈련 데이터에서의 단어 영화의 등장 횟수 : 45791 훈련 데이터에서의 단어 공감의 등장 횟수 : 756 빈도수가 높은 순서로 정렬하자. vocab = sorted(word_counts, key=word_counts.get, reverse=True) print('등장 빈도수 상위 10개 단어') print(vocab[:10]) 등장 빈도수 상위 10개 단어 ['영화', '보', '있', '없', '좋', '나', '었', '만', '는데', '너무'] 빈도수가 낮은 단어는 배제하려고 한다. 등장 빈도가 3회 미만인 단어가 얼마나 되는지 보자. threshold = 3 total_cnt = len(word_counts) # 단어의 수 rare_cnt = 0 # 등장 빈도수가 threshold보다 작은 단어의 개수를 카운트 total_freq = 0 # 훈련 데이터의 전체 단어 빈도수 총 합 rare_freq = 0 # 등장 빈도수가 threshold보다 작은 단어의 등장 빈도수의 총 합 # 단어와 빈도수의 쌍(pair)을 key와 value로 받는다. for key, value in word_counts.items(): total_freq = total_freq + value # 단어의 등장 빈도수가 threshold보다 작으면 if(value < threshold): rare_cnt = rare_cnt + 1 rare_freq = rare_freq + value print('단어 집합(vocabulary)의 크기 :',total_cnt) print('등장 빈도가 %s번 이하인 희귀 단어의 수: %s'%(threshold - 1, rare_cnt)) print("단어 집합에서 희귀 단어의 비율:", (rare_cnt / total_cnt)*100) print("전체 등장 빈도에서 희귀 단어 등장 빈도 비율:", (rare_freq / total_freq)*100) 단어 집합(vocabulary)의 크기 : 45296 등장 빈도가 2번 이하인 희귀 단어의 수: 26105 단어 집합에서 희귀 단어의 비율: 57.63202048746025 전체 등장 빈도에서 희귀 단어 등장 빈도 비율: 2.2769635638286716 2회 이하로 나온 단어가 단어 집합의 절반 이상(57.6%)을 차지하지만, 실제 등장 빈도로는 2.27%밖에 안 된다. 별로 중요하지 않은 단어들이니 정수 인코딩에서 배제한다. 단어 집합의 최대 크기를 이 단어들을 뺀 개수로 제한하자. # 전체 단어 개수 중 빈도수 2이하인 단어는 제거. vocab_size = total_cnt - rare_cnt vocab = vocab[:vocab_size] print('단어 집합의 크기 :', len(vocab)) 단어 집합의 크기 : 19191 단어 집합의 크기는 19,191이다. 여기에 패딩과 모르는 단어를 위해 <PAD> 와 <UNK> 를 추가한다. 실제 의미는 없지만 특별한 용도로 쓰는 이런 단어를 스페셜 토큰 (Special Token)이라고 한다. 각각 0과 1에 할당한다. 4.4에서 <unk> , <pad> 를 단어 집합에 넣었던 것과 같다. word_to_index = {} word_to_index['<PAD>'] = 0 word_to_index['<UNK>'] = 1 for index, word in enumerate(vocab) : word_to_index[word] = index + 2 vocab_size = len(word_to_index) print('패딩 토큰과 UNK 토큰을 고려한 단어 집합의 크기 :', vocab_size) 패딩 토큰과 UNK 토큰을 고려한 단어 집합의 크기 : 19193 print('단어 <PAD>와 맵핑되는 정수 :', word_to_index['<PAD>']) print('단어 <UNK>와 맵핑되는 정수 :', word_to_index['<UNK>']) print('단어 영화와 맵핑되는 정수 :', word_to_index['영화']) 단어 <PAD>와 맵핑되는 정수 : 0 단어 <UNK>와 맵핑되는 정수 : 1 단어 영화와 맵핑되는 정수 : 2 빈도 1위인 '영화'가 스페셜 토큰 바로 다음인 2가 됐다. 6) 정수 인코딩 이제 정수 인코딩을 하자. 단어 집합에 없는 단어는 일괄로 <UNK> , 즉 정수 1로 매핑한다. def texts_to_sequences(tokenized_X_data, word_to_index): encoded_X_data = [] for sent in tokenized_X_data: index_sequences = [] for word in sent: try: index_sequences.ap
컴퓨터를 사용하면서 우리는 운영체제라는 말을 자주 접한다. Windows, macOS, Linux, Android, iOS처럼 익숙한 이름들이 모두 운영체제에 해당한다. 그런데 운영체제는 정확히 어떤 일을 하고 있을까? 이번 글에서는 운영체제가 무엇인지부터 시작해 커널, 사용자 모드와 커널 모드, 시스템 호출 까지 운영체제의 기본 구조를 정리해본다. 1. 운영체제란? 운영체제의 정의 운영체제(OS, Operating System)는 실행할 프로그램에 필요한 자원을 할당하고, 프로그램이 올바르게 실행되도록 돕는 특별한 프로그램 이다. 쉽게 말하면 운영체제는 응용 프로그램과 하드웨어 사이에서 다리 역할을 하는 프로그램 이라고 볼 수 있다. 프로그램이 실행되려면 CPU, 메모리, 저장장치, 입출력장치 등의 자원이 필요하다. 하지만 여러 프로그램이 동시에 실행된다면 문제가 생긴다. 어떤 프로그램에 CPU를 먼저 사용할 수 있게 할까? 메모리는 얼마나 할당해야 할까? 여러 프로그램이 하나의 입출력장치를 사용하면 어떻게 해야 할까? 저장장치의 파일은 어떻게 관리할까? 운영체제는 이러한 자원들을 관리하면서 프로그램이 정상적으로 실행될 수 있도록 돕는다. 운영체제를 한 나라의 자원을 효율적으로 배분하는 정부 에 비유하면 이해하기 쉽다. 국가가 한정된 자원을 필요한 곳에 배분하듯이, 운영체제도 컴퓨터의 한정된 자원을 여러 프로그램에 적절하게 배분한다. 대표적인 운영체제 Windows macOS Linux Android iOS 운영체제는 어디에서 실행될까? 운영체제는 메모리에서 커널 영역 이라는 특별한 공간에 적재되어 실행된다. 반면 일반적인 응용 프로그램은 사용자 영역 에 적재된다. 운영체제의 핵심적인 부분은 커널 영역에서 실행되고, 일반적인 응용 프로그램은 사용자 영역에서 실행된다고 이해하면 된다. 2. 운영체제는 어떤 일을 할까? 운영체제의 핵심 역할은 컴퓨터의 자원을 관리하는 것이다. 1 메모리 자원 관리 실행 중인 프로그램을 메모리의 빈 공간에 적재하고, 더 이상 사용하지 않는 프로그램을 정리하면서 메모리를 관리한다. 즉, 여러 프로그램이 메모리를 효율적으로 사용할 수 있도록 관리하는 역할이다. 2 CPU 자원 할당 CPU는 한정된 자원이다. 여러 프로그램이 동시에 실행되는 환경에서는 어떤 프로그램을 먼저 실행할지, 얼마나 오랫동안 CPU를 사용할지 결정해야 한다. 운영체제는 CPU 스케줄링 을 통해 이러한 작업을 관리한다. 3 입출력장치 관리 프린터와 같은 입출력장치를 여러 프로그램이 동시에 사용하려고 하면 충돌이 발생할 수 있다. 운영체제는 이러한 장치의 사용 순서를 조정하여 문제가 발생하지 않도록 관리한다. 4 파일 시스템 관리 저장장치에 있는 정보를 파일과 폴더(디렉터리) 단위로 관리한다. 우리가 컴퓨터에서 파일을 만들고, 삭제하고, 폴더에 정리할 수 있는 것도 운영체제의 파일 시스템 관리와 관련되어 있다. 3. 개발자가 운영체제를 알아야 하는 이유 운영체제는 컴퓨터를 사용하는 사람뿐만 아니라 개발자에게도 중요한 개념이다. 하드웨어 조작을 대신해준다 개발자가 프로그램을 만들 때 하드웨어를 직접 제어하는 코드를 모두 작성해야 한다면 매우 복잡해질 것이다. 운영체제가 하드웨어에 접근하고 조작하는 작업을 대신 처리하기 때문에 개발자는 운영체제에서 제공하는 기능을 이용할 수 있다. 문제 해결에 도움이 된다 프로그램을 실행하다 보면 메모리 부족이나 파일 접근 문제처럼 다양한 오류를 만날 수 있다. 운영체제는 하드웨어와 가까운 곳에서 프로그램의 실행을 관리하기 때문에 문제 상황을 감지하고 오류 정보를 제공하기도 한다. 따라서 운영체제의 동작 방식을 이해하면 오류가 발생했을 때 단순히 에러 메시지를 보는 것을 넘어, 왜 그런 문제가 발생했는지 추적하는 데 도움 이 된다. 기술 면접에서도 자주 다뤄진다 운영체제는 개발자가 알아야 할 대표적인 CS 기초 지식 중 하나다. 특히 프로세스, 스레드, 메모리, CPU 스케줄링, 동기화 등의 개념은 이후 다른 CS 개념을 이해하는 데에도 연결된다. 4. 운영체제의 핵심, 커널 운영체제는 매우 규모가 큰 프로그램이다. 그중에서도 운영체제의 핵심적인 서비스를 담당하는 부분을 커널(Kernel) 이라고 한다. 커널은 컴퓨터의 자원에 접근하고 조작하는 기능과 프로그램이 안전하게 실행될 수 있도록 관리하는 기능을 담당한다. 쉽게 표현하면, 운영체제에서 가장 핵심적인 일을 담당하는 부분이 커널이다. 운영체제의 종류는 다양하지만, 운영체제의 핵심적인 역할을 담당하는 커널의 기능은 서로 비슷한 부분이 많다. 그래서 전공 서적에서는 운영체제와 커널을 거의 같은 의미로 사용하는 경우도 있다. 5. 사용자와 컴퓨터를 연결하는 UI 운영체제에는 사용자와 컴퓨터가 상호작용할 수 있도록 하는 사용자 인터페이스(UI) 도 존재한다. UI는 크게 두 가지 방식으로 생각할 수 있다. GUI GUI(Graphical User Interface) 는 아이콘이나 마우스 등을 이용해 컴퓨터와 상호작용하는 방식이다. 우리가 평소 Windows나 macOS를 사용할 때 보는 화면이 대표적인 예다. CLI CLI(Command Line Interface) 는 명령어를 직접 입력하여 컴퓨터에 작업을 요청하는 방식이다. 터미널이나 명령 프롬프트를 사용하는 것이 대표적인 예다. GUI → 아이콘, 창, 마우스 등을 이용 CLI → 명령어를 직접 입력 여기서 한 가지 기억해야 할 점이 있다. UI는 운영체제에 포함될 수 있지만, 커널 그 자체는 아니다. 커널은 운영체제의 핵심 기능을 담당하는 부분이고, UI는 사용자가 운영체제와 상호작용할 수 있도록 하는 통로라고 이해하면 된다. 6. 사용자 모드와 커널 모드 운영체제를 이해할 때 중요한 개념 중 하나가 이중 모드 다. 이중 모드란? CPU가 명령어를 실행하는 모드를 크게 사용자 모드 커널 모드 로 구분하는 방식이다. 이렇게 모드를 구분하는 이유는 응용 프로그램이 컴퓨터의 모든 자원에 마음대로 접근하지 못하도록 하기 위해서 다. 사용자 모드 사용자 모드에서는 운영체제의 서비스를 제한적으로 이용할 수 있다. 커널 영역의 코드를 직접 실행할 수 없다. 시스템의 자원에 직접 접근할 수 없다. 즉, 일반적인 응용 프로그램은 사용자 모드에서 실행되며 중요한 자원에 마음대로 접근할 수 없다. 커널 모드 커널 모드에서는 운영체제의 서비스를 제공받을 수 있다. 또한 자원에 접근하는 것을 비롯해 사용자 모드보다 더 많은 명령을 실행할 수 있다. 정리하면 다음과 같다. 구분 사용자 모드 커널 모드 커널 영역 코드 실행 불가능 가능 자원에 대한 접근 제한됨 가능 목적 일반적인 프로그램 실행 운영체제의 핵심 기능 수행 현재 CPU가 어떤 모드에서 실행되고 있는지는 CPU 내부의 플래그 레지스터를 통해 확인할 수 있다. 7. 시스템 호출(System Call) 그렇다면 사용자 모드에서 실행되는 프로그램은 운영체제의 기능을 어떻게 사용할까? 여기서 등장하는 것이 시스템 호출(System Call) 이다. 응용 프로그램이 운영체제의 서비스를 이용하려면 시스템 호출을 통해 운영체제에 요청한다. 즉, 응용 프로그램 ↓ 사용자 모드 ↓ 시스템 호출 ↓ 커널 모드 ↓ 운영체제 기능 수행 ↓ 사용자 프로그램으로 복귀 와 같은 흐름으로 이해할 수 있다. 시스템 호출은 커널 모드로 전환하여 운영체제의 서비스를 요청하기 위해 호출하는 것 이며, 일종의 소프트웨어 인터럽트라고 볼 수 있다. 예를 들어 프로그램이 파일을 읽거나 저장장치에 접근하는 등의 작업을 운영체제에 요청할 때 시스템 호출이 사용될 수 있다. 8. 운영체제가 제공하는 핵심 서비스 지금까지 내용을 조금 더 넓게 보면 운영체제의 핵심 서비스는 크게 세 가지로 정리할 수 있다. 프로세스 관리 운영체제는 실행 중인 프로그램인 프로세스 를 관리한다. 수많은 프로세스가 생성되고 실행되고 종료되기 때문에 운영체제는 이들을 체계적으로 관리해야 한다. 여기에는 다음과 같은 내용이 포함된다. 프로세스와 스레드 관리 프로세스 동기화 교착 상태 해결 자원 접근 및 할당 운영체제는 다양한 컴퓨터 자원을 관리한다. CPU 메모리 입출력장치 예를 들어 CPU를 어떤 프로세스에 먼저 할당할지 결정하는 것이 CPU 스케줄링이다. 파일 시스템 관리 저장장치에 있는 정보를 파일이라는 단위로 관리하고, 파일을 폴더(디렉터리) 단위로 묶어 관리한다. 마무리 운영체제를 처음 공부하면 용어가 많아서 어렵게 느껴질 수 있다. 하지만 핵심을 하나로 정리하면 조금 단순해진다. 운영체제는 프로그램이 컴퓨터의 자원을 안전하고 효율적으로 사용할 수 있도록 관리하는 프로그램이다. 그리고 그 중심에는 커널 이 있다. 응용 프로그램은 사용자 모드에서 실행되고, 운영체제의 기능이 필요할 때 시스템 호출 을 통해 커널에 요청한다. 응용 프로그램 ↓ 사용자 모드 ↓ 시스템 호출 ↓ 커널 ↓ CPU · 메모리 · 입출력장치 · 파일 시스템 관리 다음 글에서는 여기서 한 단계 더 들어가서, 운영체제가 실제로 프로그램을 어떻게 관리하는지 알아본다. 다음 주제는 프로세스와 스레드 다.