프로그래밍 언어 활용

운영체제 기초 활용

출제기준 세세항목: 운영체제 종류 · 메모리 관리 · 프로세스 스케쥴링 · 환경변수 · shell script

핵심 요약

운영체제의 역할과 종류

운영체제는 컴퓨터의 하드웨어 자원(CPU, 메모리, 저장 장치, 입출력 장치)을 관리하고, 사용자와 응용 프로그램이 이 자원을 편하게 쓸 수 있게 이어 주는 시스템 소프트웨어입니다. 건물 관리인이 전기, 물, 엘리베이터를 관리해 입주자들이 신경 쓰지 않고 생활하게 해 주는 것과 같습니다. 주요 일은 프로세스 관리, 메모리 관리, 파일 관리, 입출력 장치 관리, 보안·보호입니다.

운영체제의 성능은 네 가지 기준으로 봅니다. 일정 시간 동안 처리한 일의 양인 처리 능력(Throughput), 일을 맡긴 뒤 결과를 받기까지 걸린 반환 시간(Turnaround Time), 필요할 때 바로 쓸 수 있는 정도인 사용 가능도(Availability), 문제없이 정확하게 동작하는 정도인 신뢰도(Reliability)입니다. 처리 능력·사용 가능도·신뢰도는 높을수록, 반환 시간은 짧을수록 좋습니다.

대표적인 운영체제로 개인용 컴퓨터의 윈도우(Windows)와 macOS, 서버에서 많이 쓰는 유닉스(UNIX)와 리눅스(Linux), 휴대 기기의 안드로이드(Android)와 iOS가 있습니다. 유닉스는 대부분 C 언어로 작성되어 다른 기계로 옮기기 쉽고(이식성), 여러 사용자가 동시에 여러 작업을 할 수 있으며(다중 사용자·다중 작업), 파일을 나무 모양의 계층 구조로 관리합니다. 리눅스는 유닉스와 비슷하게 동작하도록 만든 공개 운영체제로 누구나 소스 코드를 보고 고칠 수 있고, 안드로이드는 리눅스 커널을 바탕으로 만들어졌습니다.

유닉스 계열 운영체제는 커널, 셸, 유틸리티로 이뤄집니다. 커널은 하드웨어를 직접 관리하는 핵심부로 프로세스, 메모리, 파일, 입출력을 맡고 컴퓨터가 켜져 있는 동안 메모리에 늘 올라와 있습니다. 셸은 사용자가 입력한 명령을 해석해 커널에 전달하는 명령어 해석기로, sh, bash, csh, ksh 등 여러 종류가 있습니다. 유틸리티는 편집기, 컴파일러 같은 도구 프로그램입니다.

프로세스와 스레드, 상태 전이

프로세스는 실행 중인 프로그램입니다. 디스크에 저장된 프로그램은 악보이고, 그것을 메모리에 올려 실제로 연주하고 있는 것이 프로세스입니다. 운영체제는 프로세스마다 프로세스 제어 블록(PCB)을 만들어 프로세스 식별 번호(PID), 현재 상태, 다음에 실행할 명령의 위치(프로그램 카운터), 레지스터 값, 우선순위, 메모리 정보 등을 기록합니다.

스레드는 프로세스 안에서 실제로 실행되는 흐름의 단위입니다. 한 프로세스 안의 스레드들은 코드, 데이터, 힙 영역을 함께 쓰고, 각자 스택과 레지스터만 따로 가집니다. 그래서 프로세스를 새로 만드는 것보다 스레드를 만드는 것이 가볍고, 스레드끼리 데이터를 주고받기도 쉽습니다. CPU가 실행하던 프로세스를 바꿀 때 지금 상태를 PCB에 저장하고 다음 것의 상태를 불러오는 일을 문맥 교환(Context Switching)이라고 하는데, 이것이 잦으면 실제 일보다 교환에 시간을 더 쓰게 됩니다.

프로세스는 다섯 가지 상태를 오갑니다. 생성(New)된 프로세스는 준비(Ready) 상태에서 CPU 차례를 기다리고, CPU를 받으면 실행(Running) 상태가 됩니다. 실행 중에 입출력처럼 기다려야 할 일이 생기면 대기(Waiting, Blocked) 상태로 가고, 일을 다 마치면 종료(Terminated)됩니다.

상태가 바뀌는 순간에도 이름이 있습니다. 준비 → 실행은 디스패치(Dispatch), 실행 → 준비는 받은 CPU 시간이 다 되어 빼앗기는 타이머 런아웃(Timer Run Out), 실행 → 대기는 입출력 등을 기다리러 가는 블록(Block), 대기 → 준비는 기다리던 일이 끝나 다시 줄을 서는 웨이크업(Wake Up)입니다. 대기 상태에서 곧바로 실행으로 갈 수는 없고, 반드시 준비 상태를 거친다는 점을 기억하세요.

프로세스 스케줄링: 누구에게 CPU를 줄까

준비 상태의 프로세스가 여럿이면 누구에게 먼저 CPU를 줄지 정해야 합니다. 이것이 프로세스 스케줄링입니다. 실행 중인 프로세스에게서 CPU를 강제로 빼앗을 수 있으면 선점(Preemptive), 스스로 끝내거나 내놓을 때까지 기다려야 하면 비선점(Non-preemptive) 방식입니다. 선점은 급한 일을 빨리 처리하기 좋지만 문맥 교환이 늘고, 비선점은 단순하지만 긴 작업 뒤에서 짧은 작업이 오래 기다릴 수 있습니다.

비선점 방식부터 봅니다. FCFS(First Come First Served, 선입선출)는 먼저 온 순서대로 처리합니다. SJF(Shortest Job First)는 실행 시간이 가장 짧은 것부터 처리해 평균 대기 시간이 가장 짧지만, 긴 작업이 계속 밀려 무한히 기다리는 기아 상태가 생길 수 있습니다. HRN(Highest Response-ratio Next)은 이를 보완해 우선순위를 (대기 시간 + 서비스 시간) ÷ 서비스 시간으로 계산하고 값이 큰 것부터 실행합니다. 오래 기다릴수록 값이 커지므로 긴 작업도 언젠가는 차례가 옵니다. 예를 들어 10초 기다린 5초짜리 작업은 (10 + 5) ÷ 5 = 3입니다.

선점 방식으로는 RR(Round Robin)이 대표적입니다. 모든 프로세스에 같은 크기의 시간 할당량(타임 슬라이스)을 주고, 그 시간 안에 못 끝내면 준비 줄의 맨 뒤로 보냅니다. 할당량이 너무 크면 FCFS와 같아지고, 너무 작으면 문맥 교환이 지나치게 잦아집니다. SRT(Shortest Remaining Time)는 SJF의 선점 버전으로, 새 프로세스가 도착할 때마다 남은 실행 시간이 가장 짧은 것에게 CPU를 넘깁니다. 우선순위 스케줄링은 선점·비선점 모두 가능하며, 오래 기다린 프로세스의 우선순위를 조금씩 올려 기아를 막는 방법을 에이징(Aging)이라고 합니다. 이 밖에 작업 종류별로 여러 줄을 두는 다단계 큐, 프로세스가 줄 사이를 옮겨 다닐 수 있는 다단계 피드백 큐가 있습니다.

계산 문제는 시간표를 그려 풀면 쉽습니다. 반환 시간은 종료 시각 - 도착 시각, 대기 시간은 반환 시간 - 실행 시간입니다. 세 작업 A(6초), B(2초), C(4초)가 모두 0초에 도착했고 SJF로 처리한다면 B(0~2), C(2~6), A(6~12) 순서이고, 대기 시간은 B 0, C 2, A 6으로 평균 8 ÷ 3 ≈ 2.67초입니다.

교착상태: 서로 양보하지 않아 멈춘 상태

교착상태(Deadlock)는 둘 이상의 프로세스가 서로 상대가 가진 자원을 기다리느라 아무도 앞으로 나아가지 못하는 상태입니다. 좁은 다리 양쪽에서 차 두 대가 마주 보고 서서 서로 비켜 주기만 기다리는 상황과 같습니다.

교착상태는 네 가지 조건이 모두 갖춰질 때 생깁니다. 상호 배제(Mutual Exclusion)는 자원을 한 번에 한 프로세스만 쓸 수 있는 것, 점유와 대기(Hold and Wait)는 자원을 쥔 채 다른 자원을 기다리는 것, 비선점(Non-preemption)은 다른 프로세스가 쥔 자원을 강제로 빼앗을 수 없는 것, 환형 대기(Circular Wait)는 기다림이 원을 이뤄 꼬리를 무는 것입니다. 넷 중 하나만 깨져도 교착상태는 생기지 않습니다.

대처 방법은 네 가지입니다. 예방(Prevention)은 네 조건 중 하나가 아예 성립하지 않게 미리 막는 방법으로, 필요한 자원을 한꺼번에 받게 하거나(점유와 대기 부정) 자원에 번호를 매겨 정해진 순서로만 요청하게 하는(환형 대기 부정) 식입니다. 회피(Avoidance)는 자원을 줄 때마다 안전한 상태인지 따져 보고 위험하면 주지 않는 방법으로, 은행원 알고리즘(Banker's Algorithm)이 대표적입니다. 발견(Detection)은 자원 할당 그래프 등으로 교착상태가 생겼는지 검사하는 것이고, 회복(Recovery)은 생긴 교착상태를 프로세스를 강제로 끝내거나 자원을 빼앗아 푸는 것입니다.

메모리 관리와 페이지 교체

메모리 관리는 한정된 주기억장치를 여러 프로세스에 어떻게 나눠 줄지 정하는 일입니다. 관리 전략은 언제 가져올지 정하는 반입(Fetch), 어디에 놓을지 정하는 배치(Placement), 자리가 없을 때 무엇을 내보낼지 정하는 교체(Replacement)로 나뉩니다. 배치 전략에는 들어갈 수 있는 첫 빈 공간에 넣는 최초 적합(First Fit), 남는 공간이 가장 작은 곳에 넣는 최적 적합(Best Fit), 남는 공간이 가장 큰 곳에 넣는 최악 적합(Worst Fit)이 있습니다. 빈 공간이 차례로 30KB, 20KB, 50KB, 19KB이고 18KB를 넣는다면 최초 적합은 30KB, 최적 적합은 19KB, 최악 적합은 50KB에 넣습니다.

빈 공간이 어중간하게 남는 것을 단편화라 합니다. 배정한 칸이 필요보다 커서 칸 안에 남는 공간은 내부 단편화, 빈 공간이 여기저기 잘게 흩어져 합치면 충분한데도 못 쓰는 것은 외부 단편화입니다. 실제 메모리보다 큰 프로그램도 돌릴 수 있게 보조기억장치를 메모리처럼 쓰는 가상 메모리에서는 두 방식을 씁니다. 페이징(Paging)은 프로그램과 메모리를 같은 크기의 조각(페이지와 프레임)으로 나눠 외부 단편화가 없지만, 마지막 페이지에 내부 단편화가 생길 수 있습니다. 세그먼테이션(Segmentation)은 함수나 데이터처럼 논리적인 단위로 크기가 다르게 나누므로 내부 단편화는 없지만 외부 단편화가 생길 수 있습니다.

필요한 페이지가 메모리에 없는 것을 페이지 부재(Page Fault)라 하고, 빈 프레임이 없으면 하나를 골라 내보내야 합니다. OPT(최적)는 앞으로 가장 오랫동안 쓰지 않을 페이지를 내보내 부재가 가장 적지만, 미래를 알아야 하므로 비교 기준으로만 씁니다. FIFO는 가장 먼저 들어온 페이지를, LRU(Least Recently Used)는 가장 오랫동안 쓰지 않은 페이지를, LFU(Least Frequently Used)는 쓴 횟수가 가장 적은 페이지를 내보냅니다. NUR(Not Used Recently)은 참조 비트와 변경 비트로 최근에 안 쓴 페이지를 고릅니다. FIFO는 프레임을 늘렸는데 오히려 부재가 늘어나는 벨레이디의 모순이 생길 수 있습니다.

직접 따라가 봅시다. 프레임 3개, 참조 순서가 1, 3, 1, 4, 2, 1, 5라면 FIFO는 1, 3이 부재, 1 적중, 4 부재, 2 부재(먼저 온 1을 내보냄), 1 부재(3을 내보냄), 5 부재(4를 내보냄)로 부재 6번입니다. LRU는 2가 들어올 때 가장 오래 안 쓴 3을 내보내므로 다음 1이 적중하고, 5가 들어올 때 4를 내보내 부재 5번입니다. 페이지 부재가 너무 잦아 CPU가 실제 일보다 페이지 교체에 시간을 다 쓰는 현상이 스래싱(Thrashing)입니다. 프로세스가 자주 쓰는 페이지 묶음(워킹 셋)은 메모리에 남겨 두는 방식으로 막으며, 이는 최근에 쓴 곳을 또 쓰고(시간 지역성) 가까운 곳을 이어 쓰는(공간 지역성) 성질에 기댑니다.

리눅스 기본 명령과 권한

리눅스에서 자주 쓰는 명령은 무엇을 하는지와 함께 이름을 정확히 기억해 두어야 합니다. 디렉터리 이동 cd, 현재 위치 출력 pwd, 목록 보기 ls(ls -l은 자세히, ls -a는 숨김 파일까지), 디렉터리 만들기 mkdir, 빈 디렉터리 지우기 rmdir, 파일 지우기 rm(rm -r은 디렉터리째), 복사 cp, 이동·이름 바꾸기 mv, 내용 보기 cat, 문자열 찾기 grep, 파일 찾기 find, 실행 중인 프로세스 보기 ps, 프로세스 끝내기 kill이 있습니다. 윈도우 명령 프롬프트에서는 목록 보기가 dir, 복사가 copy, 삭제가 del, 내용 보기가 type, 화면 지우기가 cls입니다.

ls -l로 보면 -rwxr-xr--처럼 권한이 나옵니다. 맨 앞 한 글자는 종류로 -는 일반 파일, d는 디렉터리, l은 링크입니다. 나머지 아홉 글자는 세 글자씩 소유자, 그룹, 기타 사용자의 권한이고, r은 읽기, w는 쓰기, x는 실행입니다. 권한을 숫자로 쓸 때는 r=4, w=2, x=1을 더해 rwx는 7, r-x는 5, r--는 4이므로 -rwxr-xr--는 754입니다.

권한은 chmod로 바꿉니다. chmod 754 run.sh처럼 숫자로 주거나, chmod u+x run.sh(소유자에게 실행 권한 추가)처럼 기호로 줍니다. u는 소유자, g는 그룹, o는 기타, a는 모두입니다. 소유자는 chown, 그룹은 chgrp로 바꿉니다. umask는 새로 만드는 파일에서 빼낼 권한을 정하는 값으로, umask가 022면 파일은 666에서 빼서 644, 디렉터리는 777에서 빼서 755로 만들어집니다.

환경변수와 셸 스크립트

환경변수는 운영체제와 프로그램이 참고하는 이름 붙은 설정값입니다. 대표가 PATH로, 명령을 입력했을 때 실행 파일을 찾아볼 디렉터리 목록입니다. 그 밖에 사용자의 홈 디렉터리 HOME, 사용자 이름 USER, 쓰는 셸 SHELL, 현재 디렉터리 PWD, 언어 설정 LANG이 있고, 자바를 쓰려면 JDK 설치 위치를 JAVA_HOME에 넣어 두는 경우가 많습니다.

리눅스에서는 echo $HOME으로 값을 확인하고, env나 printenv로 전체 목록을 봅니다. export JAVA_HOME=/opt/jdk처럼 export로 지정하면 지금 셸과 거기서 실행하는 프로그램에 적용되며, unset으로 지웁니다. 로그아웃해도 남기려면 ~/.bashrc나 ~/.profile 같은 설정 파일에 적어 둡니다. 윈도우에서는 echo %PATH%로 확인하고, set으로 지금 창에만, setx나 시스템 속성 화면으로 계속 남게 지정합니다.

셸 스크립트는 셸 명령을 파일에 모아 차례로 실행하게 만든 프로그램입니다. 첫 줄의 #!/bin/bash(셔뱅)는 이 파일을 어떤 셸로 실행할지 알려 줍니다. 변수는 NAME=kim처럼 등호 양옆에 공백 없이 만들고 $NAME으로 꺼내 씁니다. 스크립트에 넘긴 인자는 $1, $2로, 스크립트 이름은 $0, 인자 개수는 $#, 직전 명령의 종료 상태(0이면 성공)는 $?로 읽습니다. 계산은 $((a + b))처럼 씁니다.

조건문은 if [ $n -gt 10 ]; then ... elif ...; else ...; fi 모양이며, 대괄호 안쪽 양옆에 공백이 꼭 있어야 합니다. 숫자 비교는 -eq(같다), -ne(다르다), -gt(크다), -lt(작다), -ge(크거나 같다), -le(작거나 같다)를 씁니다. 반복문은 for i in 1 2 3; do echo $i; done이나 while [ 조건 ]; do ... done으로, 여러 경우 나누기는 case ... in ... esac로 씁니다. 만든 스크립트는 chmod +x backup.sh로 실행 권한을 준 뒤 ./backup.sh로 실행합니다.

실무에서는 이렇게 쓰여요

명령을 못 찾는 서버

새 서버에 JDK를 설치했는데 java 명령을 입력하면 'command not found'가 나왔습니다. 설치 디렉터리의 bin이 PATH에 없었기 때문이며, ~/.bashrc에 JAVA_HOME과 PATH를 export하는 줄을 추가하고 다시 로그인하자 해결되었습니다.

실행되지 않는 백업 스크립트

직접 만든 백업 스크립트를 ./backup.sh로 실행하자 'Permission denied'가 나왔습니다. ls -l로 보니 권한이 rw-r--r--(644)로 실행 권한이 없었고, chmod 755 backup.sh로 소유자는 모든 권한, 나머지는 읽기와 실행만 할 수 있게 바꿨습니다.

메모리가 부족한 분석 서버

분석 작업을 동시에 너무 많이 돌린 서버가 CPU 사용률은 낮은데 디스크만 계속 바쁘고 작업이 거의 진행되지 않았습니다. 페이지 교체가 끊임없이 일어나는 스래싱이었고, 동시에 도는 작업 수를 줄이고 메모리를 늘려 해결했습니다.

총정리 목차로