안녕하세요 마무입니다. 오늘은 "리눅스 프로세스 코드", "윈도우 프로세스 구조", "커널 프로세스 코드", "프로세스 코드"에 대해서 다뤄보도록 하겠습니다.
오늘 이 포스트를 다 읽으셔야 앞으로 배울 운영체제 관련 지식들과 프로세스에 관련된 지식들(프로세스 구조, 시그널, 시스템 콜 등등)을 공부할 때 막힘 없이 공부가 될 겁니다.
***** 이해를 위해 사전에 알아야 할 개념 *****
기본 프로그래밍 지식(변수 선언, C언어 구조체 ,#define, 함수, 선언문 바꾸기)
프로세스란, 데몬과 서비스차이, 파일 이름 끝의 ".d" 뜻, 웹서버와 브라우저 역할 살짝
****************************************************
-----목차-----
1. 왜 프로세스를 쓰는가
2. 운영체제를 만든다고 생각해보자
3. 프로세스 코드
i) task_struct
ii) 윈도우 구조체 형태
4. 정리
----------------
입니다.
더 많은 운영체제, 리눅스 지식은
리눅스 독학 페이지 : https://mamu2830.blogspot.com/p/blog-page_13.html
운영체제 독학 페이지:https://mamu2830.blogspot.com/p/blog-page_14.html
에서 찾아보세요!
1. 왜 프로세스를 쓰는가
컴퓨터 공부를 하다 보면, 정말~정말~ 많이 이 '프로세스'라는 이름을 보게 됩니다.
그리고 대부분 '프로세스'란 컴퓨터에서 프로그램이 실행돼 메모리에 올라간 것! 까지의 개념은 다들 아실 겁니다.
그러나 운영체제에 대해 더 깊숙한 공부를 하며 자꾸 이 '프로세스'와 관련된 용어와 지식에 노출될 수록 근본적인 찜찜함이 생깁니다.
예를 들어,
"운영체제에서는 프로세스 사이의 통신을 위해 '시그널'이란 것을 사용한다" 라는 글을 볼 때나,
"프로그램을 실행하면 메모리에 '텍스트, 데이터, 스택, 힙,' 등의 영역으로 나뉘어 프로그램의 데이터가 나뉜다"
"운영체제가 프로세스를 제어할 때 필요한 프로세스 상태 정보는 PCB(프로세스 제어 블록)에 저장되며 '프로세스 식별자', '프로세스 상태', '프로그램 카운터', '레지스터 저장 영역', '프로세서 스케줄링 정보', '계정 정보', '입출력 상태 정보'....등이 들어있다"
라는 글들을 볼 때에 말이죠.
이러한 글을 읽을 때마다 스믈스믈 올라오는 찜찜함이란 바로 '개념은 알겠는데 공감이 안되네'란 기분입니다
그리고 이러한 기분은 바로 커널 프로그래밍을 해본 적이 없기 때문에 생기는 겁니다.
왜냐면 우리가 작성하는 거의 99%의 프로그래밍은 바로 '운영체제'란 프로그램 위에서 작동하는 '응용프로그램'으로, 당연히 우리도 응용 프로그래밍을 해봤기 때문에 특정 응용 프로그램의 개념은 공감과 이해가 됩니다.
하지만 C언어를 이용해 만든 '운영체제'의 커널 코드는 저희가 살면서 볼 일도, 프로그래밍 해볼 일도 거의 없죠.
그렇다고 깊은 커널 이해를 위해, 커널 프로그래밍을 해보자? 음.. 그럴 시간과 능력이 되시면 정말 그만큼 좋은 경험은 없을 것 같지만 정말 어렵고 정말 많은 시간을 투자해야 할 것이라... 사실 추천하기가 좀 그렇습니다. 만약 시스템 해킹을 배우는 중이나, 운영체제 커널 개발자가 되겠다 싶은게 아니라면
그래서 저는 이번 포스트에서 커널 프로그래밍은 아니지만, '운영체제에서 왜 그런 개념을 도입했는가?"라는 것들을 예시를 통해 이해하고, 또 실제로 일부 커널 코드를 보여드리며 여러분들의 '찜찜함'을 덜어드리려고 합니다.
2. 운영체제를 만든다고 생각해보자
수~~많은 프로그램들을 관리해주는 제일 중요하며 아주 커다란 프로그램인 이 '운영체제'라는 것을 우리가 한번 만든다고 생각해봅시다.
'운영체제'란 일단 근본적으로 컴퓨터의 하드웨어(CPU, 메모리, 하드디스크, 키보드, 마우스, 모니터, 랜카드)등을 다룰 수 있게 해주고, 또 사용자가 원하는 일련의 행위(프로그램)을 실행시켜줘야겠죠.
하드웨어 관련 기능을 구현하는 것은 너무나 막연하고 어렵기에... 일단 그나마 비교적 생각하기 쉬운 사용자가 원하는 일련의 행위(프로그램)을 실행시켜주는 것만 구현해본다 가정해봅시다.
먼저, '운영체제'도 프로그램이라고 했죠? 당연히 우리가 원하는 응용프로그램의 데이터를 실행시키는 프로그래밍 기능을 구현해야할 것입니다.
그러려면 우선 응용프로그램에 들어있는 데이터를 어떻게 저장하고 사용할 지부터 정의해야겠네요. 바로 '변수' 생성이죠.
현대의 컴퓨터는 수~많은 프로그램을 프로세스로 만들어서 사용하죠, 그 말은 즉 메모리 공간에 여러 개의 프로그램을 올려야 한다는 거죠.
수 많은 프로그램의 데이터가 메모리에 올라갈 테니, 당연히 각 프로그램의 구분이 필요합니다. 이 때 프로세스마다 고유의 번호를 줘서 구분을 하면 되겠죠.
process identification을 줄여서 이 프로세스 번호를 저장하는 변수의 이름은 'pid'로 짓겠습니다.
그리고 각 프로세스가 현재 실행 중인지 아닌지 등 상태를 나타내야 하니, 이러한 상태 값을 저장하는 변수는 '상태'를 나타내는 'state'로 짓겠습니다.
그리고 크고 복잡한 대형 프로그램에서 0, 1 이런 값을 막연하게 넣으면, 이 0과 1 숫자가 어디에 쓰이는 번호인지 헷갈리므로 #define를 이용해
#define TASK_RUNNING 0 // 실행 중인 프로세스
#define TASK_INTERRUPTIBLE 1 // 휴무 상태 프로세스, interrupt를 받으면 실행 가능
이런 식으로 'state'변수에 들어가는 값을 지정해두겠습니다.
그리고 막연한 숫자인 pid 외에도 사람이 봤을 때 바로 인식할 수 있게, 프로그램(실행 파일)의 이름을 저장해둘 변수도 만들겠습니다. 실행어는 command니, 'comm'이란 이름으로 변수를 만들겠습니다.
그리고 현대의 운영체제는 아주 짧은 시간마다 여러 프로그램들을 돌아가며 CPU를 할당해 여러 프로세스를 실행하는데요, 당연히 더 중요한 프로그램이 있으면 그걸 먼저 끝내고 싶잖아요?
그러므로 프로세스마다 우선권이 있어서, 우선권을 조정해 더 빨리 실행시키거나 늦게 실행시킬 프로세스를 정할 수 있게 하고 싶네요. 그러므로 우선권(priority)을 저장할 변수 'prio'를 만들겠습니다.
또 이 프로세스를 만드는 게 끝이 아니라 지속적으로 관리를 해줘야 할 겁니다. 이 때 어떤 신호를 숫자로서 주면, 프로세스가 그 숫자에 맞는 어떤 행위를 하게 만들고 싶네요, 예를 들어 프로세스 종료해라, 대기하라 등등 말이죠.
그러므로 그 신호를 저장할 변수를 신호(signal)란 이름에 맞게 'signal'이란 변수로 저장하겠습니다.
일단 저희가 만드는 운영체제의 프로세스 골격은 간단하게 이 정도로 정의하고, 이젠 수 많은 프로그램들을 획일성 있게 만들기 위해, 지금까지 만든 "pid, state, comm, prio, signal"변수들을 구조체로 묶겠습니다.
왜냐 아쉽게도 C언어는 객체지향 언어가 아니라서 객체 개념이 없거든요 ㅠㅠ
그리고 이 구조체 이름은 프로세스와 같은 의미(해야할 일)인 'task'를 이용해, "task_struct"라고 짓겠습니다.
자 이러면 이제 앞으로 프로세스를 만들 때 'task_struct'를 이용해 똑같은 형식의 프로세스들을 찍어낼 수 있을 겁니다.
이렇게 한번 간단하게 '번호, 상태, 이름, 우선순위, 시그널 저장 변수'를 갖는 프로세스 구조체를 정의해봤습니다.
이걸 코드로 바꿔보면
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
struct task_struct {
pid_t pid;
volatile long state;
char comm[TASK_COMM_LEN];
int prio;
unsigned long signal;
};
이런 모습이겠죠.(TASK_COMM_LEN은 제가 여기선 말을 안한 '실제로 리눅스 운영제체에서 사용하는 #define으로 정의한 변수'입니다, pid_t도 운영체제 개발자가 쉽게 구분하기 위해 정의한 'pid용 변수 선언문'이라 생각하시면 됩니다, volatile은 개발자의 의도와 달리 컴파일러가 임의로 코드를 변환하지 못하게 하는 선언문입니다.)
자 그런데 실제로 리눅스 운영체제에서 프로세스를 만드는 커널 코드도 이와 비슷하게 생겼습니다, 다만 저희가 만든 간단한 기능과 달리 실제 운영체제는 어마어마한 기능들을 갖고 있고, 서로 상호작용을 해야하기에 변수가 엄~~~청 많죠.
3. 프로세스 코드
당연히 실제로 커널 코드가 어떻게 생겼는지 궁금하지 않을리가 없겠죠! 그래서 실제로 프로세스 구조를 정의하는 구조체 코드(task_struct)가 들어있는 <sched.h(schedule.header)> 를 보여드리겠습니다.
***sched.h에 들어있는 전체 코드 보기 ***
i) task_struct
제가 올린 <sched.h> 전체 코드를 보시면 알겠지만, 수~~많은 코드 내에서 'task_struct' 구조체 만 읽으려고 해도, 그 'task_struct' 구조체가 정~~말 길어서 제가 말한 "pid, state, comm, prio, signal"에서 'state'밖에 안 보이는데요, 밑을 내리며 잘 찾아보면
이렇게 pid와
파일 이름을 저장하는 comm,
signal 구조체를 저장하는 signal 변수가 있습니다.(물론 실제론 시그널은 signal_struct라는 구조체를 가집니다.)
그리고 우선권을 저장하는 prio 변수도 있습니다.
결국 우리가 당연하게 생각하던 '프로세스'라는 것도 결국 운영체제 개발자가 만든, '관리를 편하게 하기 위해 정의한 구조체' 변수였다는 것이죠.
그러니 앞으로 운영체제 공부할 때 나오는 생소한 용어들은 뜬구름 잡는 소리가 아닌, 실제로 운영체제 개발자가 특정 목적을 가지고 만든 '변수'나 '함수'라고 생각하면 된다는 겁니다.
그리고 당연히 운영체제는 매우 중요하고 규모가 큰 프로그램이기 때문에, 버그 방지나 통일성을 위해 다들 약속을 하고 메뉴얼을 만듭니다.
이러이러한 개념은 이러한 변수로 만들고, 이러한 변수의 명은 이렇게 짓자말이죠. 그리고 그러한 약속의 메뉴얼은 우리가 공부하는 '운영체제에 나오는 용어이자 개념'이 되는 것이죠.
물론 이런 메뉴얼을 만드는 것은 운영체제뿐만 아니라, 다양한 사람들이 참여하는 프로젝트 규모에선 당연한거지만요 ㅎㅎ
뭐 쓰레드(thread)? 경량 프로세스라고도 하죠? 왜 '경량 프로세스'라고 할까요? 말 그대로 운영체제 개발자가 기존 정의한 '프로세스'라는 구조체가 너무나 크기 때문에 , 기술 발전에 힘입어 새로 도입한 자원을 아낄 수 있는 '구조체'죠 ㅎㅎ 이것도 아까 제가 올린 리눅스 커널 코드를 볼 수 있는 사이트에서 'CTRL + F'를 눌러 "thread"라 쳐보면 관련된 수 많은 코드들을 볼 수 있습니다.
뭐 PCB(Process Control Block)? PCB라고 하는 개념의 구성요소가 뭐가 있었죠?
프로세스 식별자(pid), 프로세스 상태, 프로그램 카운터, 스케줄링 정보, 계정 정보, 입출력 상태 정보.....
으음~? 어디서 익숙한 느낌이 들지 않습니까? 그렇습니다~ 위에서 저희가 프로세스 관리를 위해 정의한 'task_struct' 내에 있는 변수들이죠!! 네? 프로세스 상태, 프로그램 카운터, 스케줄링 정보, 계정 정보 등등은 말을 안하지 않았냐고요?
하하.. 당연히 너무나 코드가 길어서 저 개념에 상응하는 코드들을 일일이 다 말을 할 순 없었죠, 물론 제가 위에 적은
***sched.h에 들어있는 전체 코드 보기 ***에서 ctrl+ f를 한 뒤 "struct task_struct {"를 쳐서 그 밑에 있는 코드들을 쭈욱 보시면, PCB에서 나오는 개념의 기능을 하는 코드들을 볼 수 있을겁니다.
그럼 왜 운영체제 책이나 이런데선 'task_struct'라고 하지 않고, PCB란 용어를 사용하느냐? 저희는 '리눅스'라는 커널의 코드를 본 것이지 실제로 운영체제는 다양하고, 각 운영체제마다 당연히 프로세스 구조체의 구조나 변수이름이 다르기 때문입니다.
윈도우즈만 해도 유닉스/리눅스 운영체제와 완전 다른 독보적인 코드 구조를 가지니까요
ii) 윈도우 구조체 형태
윈도우 운영체제에선 '속성 값'을 저장해 등록하는 구조체로 예시 들어보자면
typedef struct _WNDCLASSEX{
UNIT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
....
} WNDCLASSEX;
이런식으로 윈도우는 독자적으로 변수 이름 뿐만 아니라, 변수 선언어도 새로 정의해서 사용하거든요.
그래서 각 운영체제마다 코드가 다르니, 공통적인 개념 부분을 PCB라고 부르며 책에서 사용하는 것 같습니다.
그리고 'Block'이란 제가 '운영체제 독학 -> 파일시스템' 포스트에서 알려드렸지만, 운영체제에서 사용하는 저장공간을 부를 때 사용하는 용어입니다.
즉, Process Control Block? '프로세스 컨트롤하기 위해 사용하는 저장공간' --> '프로세스 관리에 사용하기 위해 메모리에 마련해놓은 변수 공간' 이렇게 되는 것이죠.
그 외에도 뭐 프로세스 구조의 데이터 영역, 스택 영역, 힙 영역? 그것도 위에 설명한 것 처럼 커널를 구성하는 어딘가에 C언어 코드로 작성 돼 있습니다 ㅎㅎ 당연히 그 프로세스 구조라는 것도 원활한 운영체제라는 프로그램 작동에 필요해서 개발자가 정의한 것이죠.
4. 정리
a) 운영체제(커널)도 결국 C언어로 만든 '프로그램'이며, 우리가 만든 프로그램은 그 '운영체제'에서 실행되는 '응용프로그램'이다
b) 프로세스란 실제로 C언어 구조체로 만든, '응용 프로그램'을 잘 관리하기 위한 데이터구조다.
c) 사람마다 코드가 다르지만 그 기능은 같은 것처럼, 운영체제도 코드 구조가 다르지만, 개념은 대부분 비슷하다.
d) 리눅스의 프로세스 관련 커널 코드들은 https://github.com/torvalds/linux/blob/master/include/linux/sched.h에서 볼 수 있다.
오늘 이렇게 막연하게 공부하던 컴퓨터 공학, 그 중 '운영체제' 개념을 더욱 와닿게 공부하기 위해 실제 운영체제의 코드들을 보고 배워봤습니다.
우리는 이로써 앞으로 배울 '시그널, 스케줄링, 시스템콜, 쓰레드' 등등을 더욱 와닿게 배울 수 있을겁니다 ㅎㅎ
오늘도 글을 읽느라 고생하셨고! 도움이 되셨다면 꼭 따뜻한 댓글 달기 및 팔로우 클릭을 해주세요! 저에게 아주 큰 힘이 돼 포스트 퀄리티 향상에 도움이 됩니다.
그럼 다음에 더 좋은 퀄리티로 찾아뵙겠습니다~
잘 읽었습니다. 도움이 많이 되었습니다.
답글삭제실레지만 마지막 즈음에 block설명하실 때 파일시스템 부분은 나중에 배우는 부분으로 보이네요.
도움이 돼 정말 다행입니다!!
삭제넵 ㅠㅠ 파일시스템 포스트는 제가 가~~장 많은 노력과 시간을 투자한 포스트지만, 그래도 정말 어렵고 긴 포스트이기 때문에
독학 하시는 분들이 읽다가 지칠까봐 일부로 독학 순서에서 조금 나중에 배치했습니다.
꾸준히 계속 댓글 달아주셔서 감사합니다!