블로그 이전했습니다. https://jeongzero.oopy.io/
kernel of linux - Introduction
본문 바로가기
컴퓨터 관련 과목/운영체제 & 커널

kernel of linux - Introduction

728x90
OLC CENTER
https://olc.kr/course/course_online_view.jsp?id=35&s_keyword=Kernel&x=0&y=0

아는 형이 리눅스 커널 관련 온라인 강좌를 추천해줘서 봤는데 예전 학교수업때 배웠던 내용이 지금 다시 새롭게 느껴지면서 뭔가 더 잘 이해가 되는거 같았다.. 그래서 강의내용을 정리하고자 한다.

1. What is an Operating System?


운영체제란 하드웨어 자원들(CPU, mem, disk, tty)을 관리하고 , 프로그램들을 지원해주는 시스템이다. 다르게 표현하면, 아래 그림의 우측에서 볼수 있듯이, OS는 하드웨어 자원들을 감싸고(감추고) 다른 프로그램들이 원할히 동작하게 도와주는 시스템이라고 볼수 있다. 하드웨어 자원들을 감싼다는 의미는, 사용자가 하드웨어 자원들의 동작을 이해할 필요가 없이 OS가 알아서 처리해준다는 의미인것 같다.

2. what is Program?


일반적으로 c 언어로 코딩을 할때, main문을 만들고 컴파일을 하면, 바이너리가 나온다. 요 바이너리가 바로 프로그램이다. 이와 마찬가지로 우리가 일반적으로 사용하는 파워포인트, 워드 이런것도 다 프로그램이다. 그렇다면 이러한 office 프로그램을 왜 하나의 프로그램으로 만들어서 사용하지 않고, 각각의 다른 별도의 프로그램을 만들어서 사용할까?

우측 상단에 나온것처럼 왜 하나의 커다란 office 프로그램이 아닌, 각각의 office 프로그램들을 분리해서 만들었을까? 많은 이유가 있겠지만 누구나 생각할수 있는것중 하나는 퍼포먼스이다. 단지 워드만 사용하고 싶다고 해도, 만약 전체 office 프로그램을 키게되면 사용하지 않는 파워포인트, 한글 이런것들도 로딩이 되기때문에 의미없는 메모리 사용,, 등의 이유로인해 성능이 저하될 것이다.

따라서 예시로 office를 전체의 프로그램이 아닌, 기능에 맞는 프로그램을 분할해 놓은것이다. 이와 마찬가지로 리눅스도 OS를 하나의 커다란 프로그램으로 만든게 아닌, kernel, shell, utility 등의 프로그램으로 분할해 놓은 것이다.

3. what is kernel?


커널 역시도 memory resident 한 독립된 프로그램이다. memory resident 란 부팅되고나서부터 꺼질때까지 항상 메모리에 상주하고 있는 녀석이다. 이와 반대로 커널이 아닌, shell, utility 등은 메모리에 있어도 되고 없어도 되는 프로그램들이다. 이것이 커널과 다른 프로그램들과의 큰 특징이다.

  • Kernel

    memory resident함. 그거 빼면 평범한 C 프로그램

  • Utility

    disk resident함. 항상 현 주소가 disk고, user가 요청을 할때 그때 메모리에 올라옴.

    그런 의미에서 Utility는 Command라고도 부른다. ( Job이라고도 부름)

  • Shell

    shell은 uility가 여러개 disk에 있는데, 그게 언제 올라오고, 언제 내려가는지를 컨트롤함. 즉, shell은 utility 중의 하나로서, 그것의 임무는 job control이다.

  • File

    sequence of bytes라고 유닉스에서 부른다. 특히 유닉스라 리눅스에서는 I/O device들도 파일로 취급한다. 그중에서도 터미널을 우리가 standard file이라고 부른다. 리눅스의 /dev에 들어가보면 hd0, hd1, tty2 등이 있는데 이것들이 전부 원형적으로는 파일처럼 보이지만, 실제로는 I/O device들고 1대1 대응되는 것이다.

4. How Kernel-Shell-Utilities are Related


맨처음에 컴퓨터 전원을 딱 키면, 부팅이 되면서 Kernel 프로그램이 제일먼저 메모리에 올라온다. 위 사진에서 a.out은 그냥 예시로 커널 프로그램이라고 생각하면 된다.

그다음에 이제 user가 터미널을 키면, 그 터미널 위에서 shell이란 프로그램이 올라온다. 그때부터 이제 커맨드를 입력받을때까지 기다린다. 사용자가 커맨드를 입력하게되면, 해당 커맨드에 해당하는 utility 프로그램을 disk에서 가져와서 실행한다.

만약 사용자가 쉘에서 ppt를 실행시키라고 명령을 내리면 쉘이 child process를 생성해서 ppt를 실행시켜준다. child process와 관련해서는 후에 강의에서 자세히 설명되기때문에 그때 정리할것이다.

리눅스는 multi-user 시스템이기 때문에 여러 사용자가 동시에 접근할 수 있다. 위 사진을 보면, A라는 유저가 터미널을 켰을때 쉘이 올라오고, 쉘은 사용자의 입력을 받고 그에 해당하는 동작을 수행한다. B라는 유저도 마찬가지이다. 결국 계층관계를 왼쪽 아래처럼 나타낼수 있다.

부팅이 되면서 커널이 메모리에 올라오고, 쉘은 사용자의 입력을 수행하기 위해 커널과의 통신을 하게 된다.

정리를 해보면 다음과 같다

- 커널은 항상 메모리에 올라와 있다
- 커널 이외것들은 전부다 utility이다. 이는 항상 디스크에 있다가 필요할 때만 메모리에 올라오고 내려간다. 이를 커맨드라고도 한다.
- 그럼 메모리에 언제 올라오고, 언제 내려가는지를 핸들링하는 역할이 바로 shell이다.

5. Linux vs Windows


아래 사진은 윈도우와 리눅스 OS의 차이점을 간략히 나타낸 그림이다.

윈도우는 SIngle-user 시스템이고, 리눅스는 Multi-user 시스템이다. 따라서 리눅스에서는 Protection을 잘 신경써야한다. (물론 윈도우도 마찬가지) 왜냐하면 멀티유저 시스템인 리눅스는 시스템안에 내 정보도 있고, 다른 사람정보도 존재하기 때문이다.

또한 멀티유저 시스템에서는 당연히 자원을 최소한으로 줄여서 써야할것이다. 하지만 single user 시스템은 내돈내산 시스템이기 때문에 자원을 최소한으로 써야할 필요가 없다.

(리눅스는 물론 여러 배포판에서 GUI로도 사용이 가능하지만, 리눅스의 원래 CUI 기반 시스템이였기 때문에 CUI를 기준으로 설명하겠다. )

윈도우를 켜면 GUI 시스템으로 왼쪽처럼 다양한 프로그램이 화면이 보인다. 내가 사용할수 있는 유틸리티들 즉 커맨드가 화면에 보이고 사용자는 단순히 클릭만 하면 바로 실행할 수 있다.

하지만 리눅스에서는 단순히 터미널에서 쉘이 사용자의 입력을 받기위해 기다리고 있는것 뿐이다. 사용자는 원하는 커맨드를 직접 입력해줘야 원하는 동작이 수행된다.

딱 봐도 윈도우와 리눅스와의 자원 활용 차이가 난다. 확실히 리눅스가 윈도우보다 최소 자원으로 동작하는것을 알수가 있다.

6. linux protection


이제 protection 얘기를 쫌더 해보자.

만일 한 프로세스가 다른 프로세의 정보를 read/write 하려면 어떨까? 당연히 싫을것이다. 악의적인 사용자가 내 개인정보를 read하거나 학력 정보를 write로 다 덮어버리면 아주 주옥같은 상황이 벌어질 것이다.

따라서 이러한 정보들은 주로 메모리 아니면 disk에 있기 때문에 다른 사람이 내 정보에 접근하는걸 막아야하기 한다.

1코어 cpu가 있다고 가정해보자. 현재 메모리에 Bob과 Dan의 프로그램이 실행되고 있다. CPU를 싫든 좋든 연산에 필요한 로직이 수행될때 Bob이나 Dan의 프로그램에서 일을 해야한다.

만약 Bob 프로그램이 CPU를 차지했다고 가정해보자. 쉘도 프로그램이라고 했기 때문에 버그가 생길수 있다. 이러한 버그로 인해 Bob 유저가 read 혹은 write 해야하는 영역이 아닌, 허용되지 않은, 디스크나 메모리에 read 혹은 write를 하는 불상사가 일어날수 있다. 의도치 않은 경우라면 버그로 인한 문제이지만, 만약 해커가 의도한 작업이라면 해킹으로 이어질수 있다.

그렇다면 어떻게 예방해야 할까?

따라서 고안된 방법이 특정 사용자의 쉘에게 CPU를 양도해도, 연산할시 I/O instruction 을 아예 못하게 막아버렸다. 여기서 막아버렸다고 해서 I/O instruction이 아예 불가능하다는 소리는 아니다.

사용자가 I/O 작업을 할때는 반드시 커널에게 요청하는 방식으로 진행할수 있다. 커널이 요청을 받게되면, 커널이 가지고 있는 function으로 I/O를 해주는데, 무조건적으로 해주는것이 아닌, 그 I/O가 정상적인를 검사를 한뒤에 해주게 된다.

위 사진을 봐보자. Bob user의 쉘에게 CPU가 양도 되었다고 가정해보자. 현재 프로그램중 I/O 연산을 진행해야한다. user 프로그램은 I/O를 직접 할수 없고, 해당 요청을 커널에게 요청한다.

즉, 커널에게 요청이 들어오면, 커널 내의 function을 이용해서 I/O를 진행하는데, 이러한 요청을 바로 System call 이라고 부른다.

7. System call


그렇다면 이러한 system call 을 도와주기 위해서 윈도우와는 다른 새로운 개념을 도입했다. 이것이 바로 하드웨어에 Binary bit mode 라는 개념이다.

우선 CPU 명령어 사이클이 돌면서 PC 레지스터에서 인출된 명령어의 주소 즉, 수행해야할 명령어의 주소를 메모리에게 보낸다. 그럼 메모리에서 해당 주소가 오게 되는데, 이게 바로 IR 레지스터에 들어가고, 해당 레지스터에 이번에 수행해야할 명령어가 오게 된다.

이러한 명령어는 우측 하단 처럼 opcode + operand 로 구성되어 있다.

IR 레지스터에 있는 명령어의 opcode을 들여다보고, operand 부분에 들어있는 주소를 메모리에서 또 가져온뒤, 실제 연산이 수행된다.

그럼 이제 cpu의 mode bit이 어떻게 사용되는지 살펴보자. 우선 mode bit이 1로 세팅되어있으면, kernel mode, 0이면 user mode라고 가정해보자.

만약 kernel mode로 세팅되어 있으면 cpu는 어떠한 메모리의 영역도 접근할수 있고, 어떠한 명령도 다 수행할 수 있다.

하지만 user mode로 되어있으면 cpu는, 단지 내 주소 영역만 접근 가능하며, I/O 명령, 파일에게 영향을 미칠수 있는 특수 명령은 수행 불가하며, 제한적인 명령어 수행이 가능하다.

위 내용을 정리해보자.

CPU 내에 mode-bit 이라는 것을 두고, 0 or 1 이냐에 따라서 하나를 커널모드, 나머지를 유저모드라고 부른다. 커널모드인 경우는 어떠한 메모리에 다 접근 가능하며, 어떠한 명령어도 다 수행가능하다. 유저모드는 제한된 메모리 접근(로컬한 영역)과, 제한된 명령어 사용만 가능하다.(priviledged op-code 수행 불가)

그럼 실제 어느 영역에서 mode bit이 검사되는지를 살펴보자.

CPU 명령어 사이클과정을 다시 살펴보면, PC 레지스터에서 메모리로 수행할 명령어의 주소를 보낼때, 그당시에 mode bit이 user 모드라면 CPU와 메모리 사이에 존재하는 MMU(memory management unit) 하드웨어가 CPU 에서 메모리에 연결되어있는 버스를 살펴보면서, 전송되는 주소는 체킹한다.

최근 컴퓨터의 MMU는 대부분 CPU안에 들어간다고 함

만약 요청한 명령어의 address가 자신의 로컬 범위를 밖에 주소위치하는 주소라면 그 즉시 CPU를 뺏겨버린다. 즉 요청한 명령어 주소를 얻지 못하고 끝나버린다.

만약 정상적으로 명령어를 가져왔다면, Decode 단계에서 opcde를 보고 어떠한 역할인지 파악을 한다. 그리고 수행되기 전에 한번더 op-code를 보고 이게 priviledged op-code인지 아닌지를 체크한다.

즉, 지금 돌아가는 프로세스가 커널 로직이 아닌데도 불구하고, I/O 명령 수행을 하려고하면 그 즉시 차단된다.

지금까지 말한걸 종합해보면, 현재 OS Kernel이 돌아가고 있으면 CPU mode bit는 kernel mode로 세팅된다. 어떠한 수행의 제한도 없다. 하지만 그 이외의 프로그램들은 전부 user-mode로 세팅되기 때문에 I/O 연산, 특별한 레지스터 접근, 현재 프로세스의 범위 밖의 주소에 접근이 불가하다.

그렇다면 생각해보자. 내가 코드를 짜고 파일을 읽고 쓰는 로직을 구현해놨다고 해보자. 현재 내 프로세는 당연히 user-mode인데 나는 I/O를 해야한다. 커널이 아닌 프로그램은 불가하다고 했는데 어떻게 I/O를 해야하나?

위 그림을 살펴보면 설명이 된다. 내가 read로 어떠한 파일을 읽어와라! 라고 코딩을 해놓으면 그건 소스코드에만 그렇게 보이는 것이다. 실제 컴파일을 하고나서 바이너리를 보면 그 안에 I/O statement가 없다.

그렇다면 실제 내가 원하는 I/O 연산은 커널에게 요청해서 수행되는 system call을 이용하게 된다. 위에 말했듯이, 소스코드에 존재하는 read 같은 함수들은 컴파일이 될때 컴파일러가 read나 write 처럼 I/O가 필요한 부분들 마다 전부 chmodk 명령을 수행하게끔 만들어 놓는다.

  • chmodk ⇒ change mode protection kernel

그럼 이제 내가 작성한 코드를 컴파일을 하고 바이너리를 실행될 것인데, 바이너리 내부에 I/O 와 관련된 로직이 수행되려고 하면 chmodk가 어떻게 수행되는지를 살펴보자.

chmodk 가 수행되었을때 하드웨어는 다음의 동작을 수행한다.

chmodk는 privileged 명령이다. 따라서 user 프로그램은 이 수행을 못하기 때문에 여기서부터 CPU 사용권한을 뺏기면서 trap이 걸리고 이때부터 trap handler 루틴이 수행된다.

이 trap handler 루틴이 수행되기 전에, mode bit을 커널 모드로 변경된다. 또한 trap이 될때 즉, system call을 통해 커널에게 요청을 할시 요청 명령이 read인지, write인지를 알려줄 파라미터가 함께 커널 trap 핸들러로 들어간다.

커널에서는 요청들어온 정보를 보고 자기가 가지고 있는 커널 내의 함수를 호출하여 기능을 수행한다.

커널이 I/O를 수행하기 전에 퍼미션을 체크한다. 즉, 사용자가 요청한 특정 디스크의 특정 섹터 주소가 얘가 접근해도 되는지에, 혹은 요청한 명령을 수행할수 있는지에대한 권한을 체킹한다.

정상적인 요청이면, 커널에서 I/O 를 수행하고 다시 trap을 통해 사용자 프로그램에게 정보를 넘겨준다. 총정리를 해보자.

  • 내가 작성한 코드에 read 같은 I/O 관련 statement가 있다
  • 이걸 컴파일하면 read같은 I/O statement는 없어지고, chmodk로 바뀐다
  • 현재 cpu의 mode bit는 user mode이다. 런타임에 실행이 되면 chmodk가 실행이 되면서 trap이 걸린다.
  • 이때 하드웨어가 자동적으로 mode bit를 kernel mode로 변경한다.
  • 현재 트랩은 커널 프로그램 안에 존재한다. trap 핸들러에서 왜 요청이 왔는지를 보고, 요청에 해당하는 커널 함수 즉, I/O 관련 함수를 호출한다.
  • 호출하면서 권한체크를 한뒤에 실제 실행이된다.
  • 그리고 나서 실행이 완료되면 mode bit를 다시 user mode로 변경한다.

위 과정이 바로 system call 시에 수행되는 전체 로직이다.

<참고>
library function call은 system call과 완전히 다르다. 전자는 함수의 copy가 내 프로그램 안으로 들어오는것이고 system call은 커널에게 필요한 함수의 요청을 수행하는 것이다. 그렇기 때문에 왜 커널이 항상 메모리에 상주 해야하는지가 설명된다.

그렇다면, 현재 내 프로그램은 user-mode이다. 헌데 I/O 관련 로직을 수행하려고 하면 kernel-mode로 바뀌고 수행한뒤 다시 user-mode로 변경된다. 이렇게 모든 프로그램은 user-mode, kerner-mode 를 반복하게 되어있다.

user-mode에서 수행될때는 유저의 프로그램안에 있는 함수를 계속해서 호출해야하니까 stack이 필요하고, kernel-mode에서도 커널 안에 있는 함수를 계속 호출해야하니까 커널 역시 stack이 필요하다. 결국 모든 프로세스는 user stack, kernel stack 2개를 포함한다. 내가 이해한게 맞다면, 이전에 블로그에 작성했던

위의 그림에서 kernel 영역과 user 영역이 설명한 그 부분인것 같다

위 그림으로 모든 설명이 다 가능하다. 결론적으로 프로그램이 수행될때 user mode와 kernel mode를 반복적으로 변경해가며 수행된다.

리눅스 man 명령어로 원하는 커맨드의 정보를 확인할 수 있는데, 위에 적힌 숫자로 커맨드인지, 시스템콜인지, library call 인지 확인할 수 있다.

8. 1강 결론


학교 수업때 배운 내용을 이렇게 다시 공부하니 뭔가 새롭다.. 더 잘 이해가 되는듯.

728x90