C#에서 모든 메모리 영역은 가비지 컬렉터에 의해서 관리되는데 관리를 벗어난 영역에 작업을 수행하기 위해서 사용한다.
따라서 C#에서 관리하는 기능을 사용하지 않고 직접 관리하는 코드를 사용하겠다는 것을 명시하여 해당 코드를 GC관리로부터 벗어나도록 할 수 있기 때문에 더 자유롭게 메모리 컨트롤이 가능하게 된다. 자유로워진 만큼 메모리 할당과 해제를 모두 직접 구현해야 하며 해당 문제로 에러가 발생하면 메모리 누수, 버퍼 오버플로, 세그먼트 폴트 등 치명적인 결함이 발생하게 된다.
발생하는 문제가 치명적인만큼 이를 다루기 위해서 unsafe 키워드를 통해서 반드시 명시된 경우에만 제어권을 얻게 한다.
기본적으로 unsafe를 사용하게 되면 c# 내부에서는 문제로 인식하고 콘솔에 경고를 보내게 된다.
사용처
사용할일이 거의 없는 키워드로 사용하는 상황도 일반적이지 않다.
포인터 사용
C#에서는 포인터 사용을 허용하지 않기 때문에 unsafe를 통해서만 직접 사용이 가능하도록 한다.
빠른 코드 실행
외부 알고리즘의 경우 빠른 코드 실행이 필요하기도 하는데 이럴 때 unsafe코드를 사용하면 성능을 향상할 수 있다.
외부 라이브러리와 상호 작용
일부 외부 라이브러리는 C#의 안전한 기능을 사용할 수 없기도 한데 이때 unsafe코드를 사용하여 외부와 상호 작용이 가능하도록 할 수 있다.
static으로 정의된 멤버는 객체 인스턴스에 속하는 것이 아닌 클래스 자체에 속하게 된다.
즉, 객체가 인스턴스화되기 전에도 해당 멤버에 접근이 가능한데 클래스 자체에 속하기 때문에 클래스 이름을 통해서 직접 호출이 가능하다.
class MyClass
{
public static int myStaticVariable;
public static void myStaticMethod(){}
}
class OtherClass
{
public void SomeFunc()
{
MyClass.myStaticVariable = 10;
MyClass.mySaticMethod();
}
}
static 멤버는 모든 인스턴스에 공유되기 때문에 한 객체에서 static 멤버에 대한 수정이 있는 경우 다른 객체에서도 동일한 값을 가지게 된다. 따라서 static 멤버는 클래스의 인스턴스와 관련이 없는 작업을 수행할 때 사용되며 일반적으로 유틸리티 클래스의 메서드나 상수 특정 클래스에서 공통으로 사용하는 변수 등을 정의할 때 static 키워드가 사용된다.
주의할 점은 모든 객체에서 공유되므로 서로 다른 스레드에서 동시에 static 멤버를 수정하는 상황이 발생할 수 있는 즉, thread-safe 하지 않기 때문에 멀티스레드 환경에서는 문제가 발생할 수 있다.
이러한 특징 때문에 static을 사용할 때는 몇가지 당연한 규칙이 있다.
우선 static 클래스의 모든 멤버는 static으로 선언되어야 한다.
클래스가 static이라는 것은 별도의 인스턴스를 생성하지 않아도 해당 클래스를 어디서든 사용이 가능하고 항시 메모리에 올라가 있는 상태라는 뜻이므로 인스턴스가 생성될 때 변수 할당이 이루어지는 일반 변수의 선언은 허용되지 않게 된다.
public static class MyStaticClass
{
// 불가능
public int number_1;
// 가능
public static int number_2;
static int number_3;
}
하지만 반대로 static 클래스가 아니어도 내부의 멤버가 static으로 선언되는 것은 가능하다.
public class MyClass
{
public static int MyStaticVar = 0;
public int MyVar = 0;
}
정리
static은 처음 선언된 이후 메모리에 상주하기 때문에 언제든 접근 가능하다.
따라서 빈번하게 사용되는 공통적인 변수의 경우 정적으로 선언해서 사용하는 것이 좋을 수 있지만 너무 많은 static을 사용하게 되면 그만큼 상주하는 메모리가 많아지는 것이기 때문에 적절한 사용이 필요하다.
기사에는 이 부분에 대해서도 제조사와 인터뷰한 내용이 있다. 일단 다람쥐똥 커피는 존재하지 않는다 대신 족제비 똥 커피는 존재하긴 하는데 해당 커피와는 관련이 없다. 콘삭커피는 커피를 연구하다가 헤이즐넛 향을 첨가하게 되었고 여기서 특유의 맛을 얻게 되었다고 한다. 그래서 다람쥐가 헤이즐넛을 좋아한다는 점과 귀엽고 친숙하기 때문에 커피 이름을 Con sóc(=다람쥐)이라고 짓게 되었다고 한다.
나도 패키지에서 가장 눈에 띄는 게 커피를 먹고 있는 다람쥐 그림이었는데 그 다람쥐 그림 때문에 루왁커피처럼 다람쥐의 배설물로 만든 것인가 생각했다. 거기다 이름도 다람쥐다 보니 베트남어를 아는 사람이었다면 더 다람쥐똥 커피가 연상되었을 것 같다. 실제로 커피를 검색해 보면 다람쥐똥 커피로 더 알려져 있어서 사람들 생각은 다 비슷하구나 생각했다.
필터를 통해서 내려 마시는 방식으로 커피를 내릴 때 풍기는 찐한 향이 좋다. 맛도 굉장히 부드럽고 커피의 향도 많이 느낄 수 있는 맛있는 커피다.
패키지에는 설탕스틱도 있는데 평소에는 커피를 마실 때 추가로 넣어서 먹지 않는데 그래도 있으니 넣고 먹어봤더니 달달하니 맛이 괜찮았다.
커피를 내려 먹어야 해서 번거로움이 있지만 향이 좋아서 그 과정마저 즐기면서 마실 수 있었다.
향도 좋고 맛도 좋고 패키지도 깔끔하고 귀여운 다람쥐도 있기 때문에 커피를 좋아하는 사람에게는 좋은 선물이라고 생각된다.
변수 앞에 위치하면 해당 변수는 읽기 전용이 되어 해당 변수가 정의된 클래스나 구조체, 메서드 등에서만 수정이 가능하며 readonly로 선언된 변수는 선언할 때 또는 생성자에서 값을 할당해야한다.
public class MyClass
{
readonly int myReadOnlyInt;
public MyClass(int value)
{
myReadonlyInt = value;
}
}
위 코드에서 myReadOnlyInt는 읽기 전용으로 선언되었기 때문에 생성자에서 값을 할당한 이후에는 변경이 불가능하다.
상수를 선언한다는 점에서 const와 비슷한데 둘의 차이를 비교할 필요가 있다.
const vs readonly
초기화 방법
const와 readonly는 초기화 방법에서부터 차이가 있다.
// 반드시 선언과 동시에 초기화 필요
const int constNum = 10;
// 선언에서 뿐만 아니라 생성자에서 값을 할당해서 초기화 할 수 있다.
readonly int readonlyNum_1 = 10;
readonly int readonlyNum_2;
public MyClass(int value)
{
readonlyNum_2 = value;
}
사용 범위
const는 클래스 멤버 또는 데이터 형식 멤버로 선언할 수 있지만 클래스 멤버 중에서도 인스턴스 멤버는 const 키워드를 사용할 수 없다. 즉 인스턴스 변수, 인스턴스 메서드 등에서는 const 키워드 사용이 불가능하다.
public class MyClass
{
public const int number = 10;
public void Test()
{
int test = number;
}
}
public static class MyStaticClass
{
public const int number = 20;
public static void Test()
{
int test = number;
}
}
public class OtherClass()
{
public void TestMethod()
{
MyClass instance = new MyClass();
// 접근 불가능함
int test1 = instance.number;
// static 클래스 인스턴스화 안됨
//MyStaticClass staticInstance = new MyStaticClass();
// 직접 호출가능
int test2 = MyStaticClass.number;
}
}
실행시간
const는 컴파일 시간에 값이 결정되기 때문에 런타임 성능이 상대적으로 좋다.
readonly는 런타임에 값을 할당할 수 있기 때문에 const보다는 조금 더 느릴 수 있다.
그렇기 때문에 런타임에 값을 결정해야할 경우에만 readonly를 사용하고 그 이외에는 const를 사용하는게 낫다.
public class MyClass
{
public const int number = DateTime.Now.Year; // 컴파일 에러 발생
public readonly int year = DateTime.Now.Year; // 실행 시간에 값이 결정됩니다.
}
const와 readonly의 가장 큰 차이점은 값이 결정되는 시점이다.
const는 컴파일 타임 readonly는 런타임
따라서 애초에 고정된 값이라면 const를 사용하지만 인스턴스가 생성될 때 값을 할당하고 그 이후에 변경되지 않도록하려면 readonly를 사용하면된다.
partial 제한자로 선언된 클래스, 구조체, 인터페이스 등은 여러 파일에 나누어 작성할 수 있다.
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public void SayHello()
{
Console.WriteLine($"Hello, my name is {FirstName} {LastName}.");
}
}
Person이라는 클래스가 하나의 파일에 선언되고 작성되어 있다. 이 클래스를 여러 파일로 나누어 선언하기 위해 partial 키워드를 사용하면 다음과 같이 사용할 수 있다.
// Person.cs
public partial class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
// Person_SayHello.cs
public partial class Person
{
public void SayHello()
{
Console.WriteLine($"Hello, my name is {FirstName} {LastName}.");
}
}
Person.cs 파일과 Person_SayHello.cs 파일은 같은 네임스페이스 안에 있으며 partial 키워드를 사용해 클래스를 선언하였기 때문에 다른 파일에서 Person 클래스를 동일하게 partial 키워드를 사용해 선언하면 이 클래스는 하나의 클래스로 인식된다. partial로 선언된 클래스는 컴파일 시점에 자동으로 하나의 클래스로 합쳐지기 때문에 별도의 참조가 필요하지 않다.
이러한 기능은 특히 협업에서 용이하게 사용이 가능하다.
큰 규모의 코드를 개발하기 위해서 여러 개발자가 협업을 하게 된다면 동시에 하나의 클래스나 구조체를 작성하는 상황이 있다. 이런 상황에서 partial 키워드를 사용하여 하나의 클래스를 여러 파일에 나누어 각자 작성할 수 있고 이렇게 하면 동시에 작성을 각자가 코드를 작성하더라도 서로 영향을 주지 않고 작업을 진행할 수 있다.
양이 많은 코드의 경우 하나의 파일에 모두 작성하게 되면 보기나 수정이 힘들어진다. 이때 partial 키워드를 사용하면 코드를 작은 단위로 분할하여 작성할 수 있기 때문에 코드 가독성도 좋아지고 유지보수도 편리해진다.
가상 메서드를 정의할 때 사용되는데 이 키워드를 통해서 메서드를 재정의할 수 있도록 허용한다.
class Base
{
public virtual void Print()
{
Console.WriteLine("Base class");
}
}
override
상속 관계에서 부모 클래스에 정의된 메서드를 자식 클래스에서 다시 정의할 때 사용된다.
class Derived
{
public override void Print()
{
Console.WriteLine("Derived class");
}
}
override 키워드를 사용하여 부모 클래스에서 정의한 메서드를 자식 클래스에서 재정의하면 자식 클래스의 인스턴스에서 호출할 때 부모 클래스와 자식 클래스의 구현 차이를 쉽게 반영할 수 있다.
Base base = new Base();
base.Print();
// "Base class"
Derived derived = new Derived();
derived.Print();
// "Derived class"
Base base2 = new Derived();
base2.Print();
// "Derived class"
C#에서는 내장 클래스인 string, int, double 등을 포함한 모든 클래스 object를 상속받기 때문에 new 키워드를 사용해서 객체를 생성할 수 있다.
int n = new int();
string s = new string();
MyStruct structInstance = new MyStruct();
MyClass classInstance = new MyClass();
하지만 내장 클래스들은 구조체로 정의되어 있기 때문에 구조체 변수를 생성할 때 new를 사용하지 않고 객체를 바로 생성할 수 있다.
int n = 0;
float f = 1.0f;
string의 경우 .Net에서 특별히 내부적으로 string literal로 정의되어 있는데 때문에 일반적인 참조 타입과는 다르게 직접 변수를 할당하여 객체가 생성이 가능하다.
* string literal : 문자열 상수는 소스 코드 상에 고정된 문자열 값이다.
Inherit
new 키워드는 상속과 관련해서도 사용이된다.
상속 관계에 있는 클래스에서 메서드, 프로퍼티, 이벤트 등의 멤버를 재정의할 때 사용되는데 일반적으로 멤버의 재정의에는 override 키워드를 사용하지만 new키워드를 통해서도 멤버의 재정의가 가능하다.
class BaseClass
{
public void Print()
{
Console.WriteLine("Base class");
}
}
class DerivedClass : BaseClass
{
public new void Print()
{
Console.WriteLine("Derived class");
}
}
여기서 new 키워드를 사용하여 재정의된 메서드는 부모 클래스에서 정의된 멤버와 자식 클래스에서 정의된 멤버가 모두 유지되는데 이는 덮어쓰는 override와 다르게 두 개의 멤버가 서로 다른 것으로 재정의된 메서드는 부모 클래스의 멤버와는 관계가 없는 새로운 멤버로 취급된다.
DerivedClass obj = new DerivedClass();
obj.Print();
// "Derived class" 출력
BaseClass obj = new DerivedClass();
obj.Print();
// "Base class" 출력
운영체제는 컴퓨터 시스템의 CPU, 메모리, 입출력 장치, 저장 장치 등의 자원을 효율적으로 관리하고 다른 소프트웨어나 사용자가 이용할 수 있도록 관리하는 인터페이스 역할을 하는 소프트웨어를 말한다.
Resource Management
자원관리, 운영체제는 컴퓨터의 자원을 효율적으로 관리하고 할당하는 역할을 한다.
대표적인 자원으로는 CPU, 메모리, 저장장치, 입출력 장치가 있다.
CPU
프로세스 스케줄링을 통해 CPU 자원을 할당하고 여러 프로세스 간의 경쟁 상황을 해결한다.
CPU를 효율적으로 사용하기 위해 실행 중인 여러 프로세스들 사이에서 CPU의 사용권을 어떻게 배분할지 결정하여 CPU의 사용률을 높이고 응답 시간을 최소화하며 프로세스의 우선순위를 지정하여 경쟁 상황 해결 및 효율적인 시스템 동작을 유지한다.
스케줄링 알고리즘에는 FCFS, SJF, Priority Scheduling, Round-Robin Scheduling 등이 있다.
FCFS, First Come First Served
프로세스 스케줄링의 가장 간단한 형태 중 하나이다.
준비 큐에 도착한 순서대로 프로세스를 처리하는 방식으로 매우 직관적이기 때문에 구현이 간단하지만 실행 시간이 긴 프로세스가 먼저 도착하면 그 이후에 도착한 짧은 실행 시간을 가진 프로세스들이 대기 시간이 길어지기 때문에 평균 대기 시간과 평균 반환 시간이 크게 증가할 수 있는 문제가 있다.
* Average Waiting Time : 평균 대기 시간, 프로세스가 대기하는 시간의 평균값
* Average Turnaround Time : 평균 반환 시간, 프로세스가 큐에서 대기하고 CPU를 사용하는 시간의 합의 평균값
* 일반적으로 두 지표가 작을수록 좋은 스케줄링 알고리즘으로 판단한다.
먼저 도착한 프로세스가 먼저 실행되기 때문에 CPU를 먼저 사용하는 프로세스는 대기 시간이 짧고, 나중에 사용하는 프로세스는 대기 시간이 길어진다. 따라서 평균 대기 시간과 평균 반환 시간은 프로세스 도착 순서에 따라 크게 달라진다. 또한 FCFS는 선점형 스케줄링이 아니기 때문에 한 번 시작된 프로세스는 CPU를 반환하기 전까지 계속 실행된다. 따라서 이 알고리즘은 대화형 시스템과 같이 응답 시간이 중요한 시스템에서는 적합하지 않다.
SJF, Shortest Job First
다음에 실행할 프로세스를 선택할 때 CPU Burst Time이 가장 짧은 프로세스를 선택하는 알고리즘이다.
CPU 버스트 시간이 짧은 작업이 먼저 실행되면 해당 작업이 빠르게 완료되면 자원을 빨리 반환할 수 있고 다른 작업도 빠르게 실행될 수 있기 때문에 때문에 평균 대기 시간을 줄일 수 있다는 장점이 있다.
Priority Scheduling
FCFS와 SJF 알고리즘은 일괄적으로 대결에서 작업을 처리하기 때문에 일부 작업이 너무 오래 실행되거나 대기하는 경우 다른 작업들은 계속해서 대기열에 쌓이게 되는 문제가 있다. 이렇게 필요한 만큼의 CPU 자원을 할당받지 못하고 대기하게 되는 상태를 Starvation라고 한다.
이 문제는 대기 시간이 긴 프로세스에게 우선순위를 부여하여 대기 시간이 길어질수록 우선순위가 높아지게 하는 방법을 통해 해결할 수 있다. 그중 Aging 기법은 SJF 알고리즘에서 버스트 타임이 높은 작업을 우선순위로 두어 문제를 해결하는 기법이다.
우선순위 스케줄링은 자원이나 시간이 많이 필요한 작업을 우선 순위로 두고 먼저 처리시켜 다음 작업을 진행할 때 자원이나 시간이 부족하여 대기 상태에 빠지지 않도록 한다. 이때 동일한 우선순위의 경우 해당 알고리즘을 통해서 처리하여 기아 상황에서도 공정한 작업 스케줄링이 가능하게 한다.
Round-Robin Scheduling
CPU 스케줄링에서 가장 일반적으로 사용되는 알고리즘이다.
시분할 시스템에서 사용되며 각 프로세스가 동일한 시간 할당량(Quantum)을 갖는다는 특징이 있으며 할당된 시간 이내에 작업이 끝나지 않으면 다른 프로세스에게 CPU를 양보하고 대기열의 끝으로 이동하는 과정을 반복한다.
* 시분할 시스템 : CPU 시간을 작은 단위로 분할하여 다수의 사용자가 동시에 컴퓨터를 사용할 수 있도록하는 기술이다.
주로 대화식 시스템에서 사용되는 것이 일반적이며 사용자가 프로세스를 시작하면 해당 프로세스는 대기열에 추가되는데 CPU는 대기열에서 가장 앞에 있는 프로세스에게 할당되고 일정한 시간 후에 다른 프로세스로 넘어가고 이 과정을 대기열이 비어 있을 때까지 반복한다.
FCFS와 마찬가지로 간단하며 쉽게 구현이 가능하지만 모든 프로세스에게 동일한 기회를 부여하기 때문에 더 공정한 스케줄링이 가능하다. 다만 할당된 시간이 작은 경우에는 자주 Context Switch이 발생하기 때문에 오버헤드 문제가 있을 수 있다. 또한 할당된 시간이 큰 경우에는 대기열에 있는 다른 프로세스들이 오랫동안 기다려야 하기 때문에 적절한 시간 할당량을 결정하는 것이 중요하다.
* Context Swtich : 문맥 교환, CPU가 현재 실행 중인 프로세스에서 다음으로 실행할 프로세스로 제어를 양도하는 과정
Memory
프로세스가 사용할 메모리 공간을 할당하고 메모리 공간을 관리한다.
운영체제에서 메모리 공간은 일반적으로 세 가지 방식으로 할당 및 관리된다.
Single Fixed Partition Allocation
단일 고정 분할 할당
메모리를 고정 크기의 분할로 나누고 각 분할을 프로세스에 할당한다.
분할 크기는 운영체제가 미리 정해놓은 것으로 프로세스의 크기가 이것보다 작아야한다.
단순한 방식이지만 메모리 이용률이 낮다는 단점이 있다.
Paing 기어
Variable Partition Allocation
가변 분할 할당
메모리를 동적으로 분할하며 프로세스에 할당하는 방식이다.
프로세스의 크기에 맞춰서 할당되기 때문에 메모리 이용률이 향상된다. 프로세스의 크기가 불규칙적이고 할당과 해제에 따른 Memory Fragment 문제가 발생할 수 있다.
Memory Fragment
메모리 단편화
메모리에서 사용 가능한 공간이 작은 조각으로 나뉘어 큰 용량의 프로세스가 할당되지 못하고 남는 작은 조각들이 늘어나는 문제를 말한다. 메모리를 효율적으로 사용하지 못하게 만들어 시스템 성능을 저하시키게 된다.
메모리 단편화는 Internal Fragment와 External Fragment 두 종류가 있다.
Internal Fragment
내부 단편화, 메모리 할당 시 요청한 프로세스크기보다 더 큰 메모리 공간을 할당하게 되어 할당된 메모리 공간 중 일부가 사용되지 않는 문제
External Fragment
외부 단편화, 메모리에서 사용 가능한 공간이 작은 조각으로 나뉘어 큰 용량의 프로세스가 할당되지 못하는 문제
이러한 메모리 단편화 문제를 해결하기 위해서 Paing과 Segmentation 기법이 사용된다.
Paging : 물리적인 메모리를 고정 크기의 블록으로 분할하여 가상 주소와 물리 주소를 매핑한다.
Segmentation : 프로그램을 논리적인 단위인 세그먼트로 분할하여 가상 주소와 물리 주소를 매핑한다. 페이징 보다 프로그램의 논리적 구조를 반영하기 쉽다.
Virtual Memory
가상 메모리
물리적인 메모리보다 큰 용량의 가상 메모리 공간을 프로세스에게 할당하여 사용하는 방식이다.
프로세스가 필요로 하는 부분만 메모리에 올려서 실행하고 나머지 부분은 디스크에 저장한다. 이 방식은 물리적인 메모리보다 큰 용량의 프로그램을 실행할 수 있게 되며 프로세스 간의 메모리 공유도 가능하다.
Storage
하드디스크 등의 저장장치를 관리하고 File System을 통해 파일을 관리한다.
* File System : 운영체제에서 파일과 디렉터리를 저장하고 검색할 수 있는 구조
파일이나 디렉토리를 저장하기 위한 블록의 할당, 디스크 공간 관리, 파일 접근 권한 관리, 파일 백업 및 복구 등의 역할을 수행하는데 일반적으로 파일 시스템은 파일과 디렉토리를 계층 구조로 구성하며 각 파일과 디렉터리는 고유한 이름을 가지고 있다.
디렉터리와 파일을 구분하는 특별한 기능을 수행하기 위한 파일을 사용하기도 하는데 이 파일은 파일 시스템의 일부이지만 일반 파일과 다른 속성을 가지고 있다. 예를 들어 리눅스에는 /dev 디렉터리에 하드웨어와 상호 작용하기 위한 특별한 파일들이 존재하는데 이러한 파일들은 일반 파일과는 달리 디바이스 파일로서 하드웨어 디바이스에 대한 인터페이스 역할을 한다. Windows에는 레지스트리 파일이 이러한 파일로 분류되며 운영체제 설정 정보를 포함하고 운영체제 및 애플리케이션의 구성을 제어하는 데 사용된다.
Input/Output
키보드, 마우스, 모니터, 프린터 등의 입출력 장치의 관리, 디바이스 드라이버를 통해 입출력을 처리한다.
Device Driver Management
장치 드라이버 관리
각각의 입출력 장치에 대해 운영체제는 해당 장치와 상호작용할 수 있는 드라이버를 관리한다. 이 드라이버는 해당 장치와 통신할 수 있는 인터페이스를 제공하며 운영체제와 프로그램 간의 데이터 전송을 담당한다.
I/O Request Management
프로그램이 입출력을 요청하면 운영체제는 이를 관리하며 각각의 입출력 요청에 대해 우선순위를 결정하여 처리한다. 이를 위해서 운영체제는 입출력 요청 큐를 유지하고 요청에 따라 적절한 장치를 할당하여 요청을 처리한다.
I/O Buffering
입출력 장치의 속도는 프로그램의 실행 속도와 차이가 있기 때문에 입출력 요청에 대한 응답을 기다리는 동안에는 다른 작업을 수행할 수 있도록 버퍼링을 수행한다. 이를 위해 운영체제는 입출력 데이터를 임시로 저장할 수 있는 입출력 버퍼를 유지한다.
Interrupt Process
입출력 작업 중에는 다양한 상황에서 인터럽트가 발생할 수 있는데 이를 위해 운영체제는 인터럽트 처리 루틴을 유지하여 각각의 인터럽트에 대해 적절한 처리를 수행한다.
I/O Protection
입출력 장치를 공유하는 다양한 프로그램이 동시에 실행될 경우 장치 접근에 대한 충돌이 발생할 수 있는데 이를 방지하기 위해 운영체제는 입출력 보호 기능을 수행하여 각각의 프로그램이 입출력 장치를 안전하게 사용할 수 있도록 보장한다.
입출력 보호 기능은 장치에 대한 접근 권한을 제어하는 기능으로 보안과 안정성을 유지한다. 사용자 프로세스가 입출력 장치를 임의로 사용하지 못하도록 하고 운영체제가 입출력 장치에 대한 접근 권한을 부여하고 사용자 프로세스는 해당 권한을 가지지 못한 상태에서는 장치에 접근할 수 없도록 한다.