커널과 가상메모리 질문있어요.

kyj_kr의 이미지

간단하게 질문드리겠습니다.

컴퓨터가 부팅되면서 커널 프로세스가 실제 메모리에 올라갑니다. 그리고 프로세스를 생성하면 각 프로세스 마다의 가상메모리 공간이 만들어지면서 윈도우 32비트 기준으로 2G의 커널영역이 만들어지겠지요.
그럼 대체 이 2G크기의 가상 커널 영역에 있는 코드는 실제 메모리에 올라와있는 커널 코드를 일부분 copy한건가요?

감사합니다.

라스코니의 이미지

질문 내용 중에 혼동이 생길수 있는 내용이 있는데요.
프로세스 생성시 만들어지는 가상 메모리 공간이 유저 프로세스 영역인지 커널영역인지 확인이 필요할 것 같고요. 만약 커널영역이라면 굳이 이미 메모리에 올라와 있는 커널 코드를 복사해서 쓸 이유가 없을 것 같습니다.

furmuwon의 이미지

100개의 process 가 있고,
각 process 마다 고유의 page table 영역 4KB 를 할당해서 있다고 가정하고
process 가 보는 memory 세계는 4KB 에 다 담겨 있다고 가정합시다.

4KB 안에 내용은 실제 memory 에 대한 map 인데
후반 2GB [3~4GB] 를 모두 같은 곳, kernel code가 있는 곳으로 지정해 놓으면

굳이 process 마다 kernel code를 copy 해서 사용할 필요 없겠죠?

kyj_kr의 이미지

가상메모리 2G 크기의 커널영역을 쓰레기 값으로 채워두는건가요? 아니면 아예 만들지조차 않는건가요?

그리고 각 프로세스 마다의 매핑테이블이 있을텐데, 모든 매핑테이블은 커널에 관한 부분은 모두 valid bit이 1인가요?

또한, 가상메모리 2G의 커널코드와 실제메모리 2G의 커널코드가 매핑이 되어있을텐데 만약 커널코드가 2G보다 커버리면 어떡하죠?

감사합니다.

익명 사용자의 이미지

커널 영역엔 커널 코드와 커널이 쓰는 데이터가 들어가야지 왜 쓰레기가 들어가요?

커널 영역 2GB가 모자란 상황은 쉽게 상상하기가 어렵습니다.
고품질 멀티미디어 데이터에 익숙해진 요즘 일반인들에게야 2GB가 작아보이겠지만, 컴퓨터의 운영에 필수적인 코드와 데이터만이 올라간다고 생각해 보면 충분하다 못해 낭비가 아닐까 싶을 정도의 공간이죠.

실제로 32비트 OS 쓰던 시절 윈도우즈에서는 user address space를 3GB 주는(=커널은 1GB만 쓰는) 옵션이 있었고, x86 리눅스는 대체로 그런 구성이 디폴트였습니다.

https://docs.microsoft.com/en-us/windows/win32/memory/4-gigabyte-tuning

kyj_kr의 이미지

가상메모리의 커널영역엔 굳이 커널코드를 copy할 필요없고 매핑테이블로 실제메모리를 가리키면 되는거 아닌가요?? 위에 답변이랑 견해가 다른건지 제가 잘못이해하고 있는건지 모르겠네요..

익명 사용자의 이미지

네. 매핑 테이블(페이지 테이블)을 이용해서 커널 영역이 커널 코드와 데이터를 가리키도록 구성하면 되는 거죠.

쓰레기 값 얘기가 왜 나오는지 모르겠습니다.

kyj_kr의 이미지

그러니까 가상메모리 커널영역에는 커널에 관한 데이터를 넣을 필요가 없지 않나요?
매핑테이블로 실제메모리에 있을 커널코드와 매핑만 시켜주면 되니까요.
그렇기 때문에 위에 답변에서도 가상메모리 커널영역에는 실제메모리의 커널코드를 copy하지 않고, 매핑 테이블로만 가리킨다고 하는거같은데 정확한 지적 바랍니다

bushi의 이미지

> 커널 영역 2GB가 모자란 상황은 쉽게 상상하기가 어렵습니다
>

램을 엄청나게 쳐묵쳐묵하는 응용대신 alloc/free 가 다양한 크기로 불규칙하게 발생하는 응용을 상상해보세요.

파편화되어 불연속인 물리 주소 공간들을 논리적으로 연속인 가상 공간 하나로 만들어 쓰자는게 물리 주소 대신 가상 주소를 사용하는 이유입니다.
하지만, 가상 주소 공간이라고 해서 파편화가 되지 말라는 법은 없습니다.
물리적으로 얼마의 램 용량이 남아있냐와는 상관없이 가상 주소 공간에서 연속된 공간을 찾지 못하면 리눅스 커널의 vmalloc() 같은 놈은 실패합니다.

익명 사용자의 이미지

음; 그건 생각 못했네요. 그런 경우도 있을 수 있겠지요.

furmuwon의 이미지

가상메모리 2G 크기의 커널영역을 쓰레기 값으로 채워두는건가요? 아니면 아예 만들지조차 않는건가요?
-> 실제 메모리 영역을 지정하고 있고
실제 커널 코드가 100MB 라면
4KB map 에서 100MB 에 해당하는 부분은 실제 메모리 주소 입니다.
나머지 2G - 100 MB 은 0x0 으로 채워져 있습니다.

또한, 가상메모리 2G의 커널코드와 실제메모리 2G의 커널코드가 매핑이 되어있을텐데 만약 커널코드가 2G보다 커버리면 어떡하죠? ->
2G 보다 큰 커널코드가 생긴다면
새로운 메커니즘이 나와야 겠죠?
쉽게 생각 할 수 있는 64bit?
커널 코드는 런타임에 스스로 커지지 않습니다.
이미 컴파일 한 뒤 바이너리에서 용량에 정해지니
2GB 보다 크다면 현재 시스템으로 안되겠구나 라고 판단 해야죠

물론 질문하시는 분이 공부하시면서 애매하거나 궁금하신부분이 많은텐데
쉽게 커널 코드만 예를 들어서 이야기 해 드립니다.

memory alloc 관련하여서는 가상메모리에 대한 이해를 좀 더 하고 난 뒤에 구분하여 보시는 것도..

위에 4KB map도 쉽게 예를 드릴려고 딱 그정도만 이야기 했습니다.

이점 유념해 주세요~
(혹시나 4KB 로 모든걸 다 가리킬 수 있어? 라고 오해하실까봐..)

추가적으로
질문자님이 다시 재질문 하는 내용을 보니
가상메모리 공간을 특정한 형태의 볼륨? 부피가 있는놈으로 보시는 것 같은데
맞나요?

가상메모리는 위치를 가리키는 이정표이지 실제 부피가 있는놈은 아닙니다~
(아 위치를 가르키기 위해, map 용량 정도의 부피가 있긴하죠 ㅎㅎ )

kyj_kr의 이미지

안녕하세요!
가상메모리의 커널영역과 실제메모리의 커널영역을 매핑 시키기 위해 페이지 테이블이 존재하고,
이 페이지 테이블에는 실제메모리의 커널프로세스 크기만큼만 매핑시키고, 나머지는 그저 0x0으로 채워져있다! 라고 이해했습니다.

그렇다면, 윈도우32비트 기준으로 커널프로세스의 크기가 2G가 넘어가는게 없고 만약 넘어간다면 쉽게 설명할 수 있는 64비트로 넘어간다는거지요?

한가지 더 질문드릴게 있습니다만, "가상메모리는 위치를 가리키는 이정표이지 실제 부피가 있는놈은 아닙니다~"
말씀하신 부분에서 부피가 실제메모리에 있지 않다는걸 알려주시는거죠? 하드디스크에서 pagefile.sys로 프로세스의 부피를 차지하고 있다고 알고있었거든요..

이른시간에도 감사합니다 ㅎㅎ

furmuwon의 이미지

커널프로세스라는 말은 갑자기 왜 등장하나요?
질문하시고 대답들 중에 커널프로세스라는 말은 하나도 없지 않나요?

그리고 보편적으로 커널프로세스라는 단어가 존재하나요?
커널쓰레드를 잘못 말씀하신건가요?

.의 이미지

상당 부분을 잘못 이해하고 계신 것 같습니다. 리버싱 등을 공부하다가
로우 레벨의 내용을 체계적으로 공부하지 않고 여기저기 인터넷 등으로만
습득하려고 할 때 보통 이런 현상을 많이 겪습니다. 위에서 많이 지적하고
계시지만 컴퓨터 구조, OS 개론책을 읽어보시는 걸 권해드립니다.
꼭 그렇게 공부해야 한다는 규칙은 없지만, 지금처럼 스스로 찾아서 습득한
내용이 이해가 잘 안 되고 잘못되어지면 나중에도 딱히 좋을 건 없습니다.

뭔가 근본적인 부분에서 헷갈려하시는 것 같아서 가능한 한 아키텍처 의존적인
부분들과 OS 의존 및 복잡한 건 다 빼고 단순화해서 몇 가지만 말씀드리겠습니다.
지금 말씀하시는 "가상 메모리" 는 전부 "가상 주소 공간" 을 의미하는 것으로
생각되므로 그걸로 통일하겠습니다. 디스크 페이징 같은 것도 없다고 치겠습니다.
물론 어차피 편의상 x86 을 기준으로 설명하게 되겠습니다만.

먼저 가상 주소 공간은 말 그대로 "가상" 입니다. 추상적인 개념이지 실제로
어디 연속적인 공간이 존재하는 게 아닙니다. 이건 아마도 아실 거 같고요.

페이지 테이블은 아시다시피 가상 주소와 물리 주소를 대응시키는 테이블입니다.
순수하게 이론적으로, 주소가 32bit(4byte) 크기로 표현된다고 할 때 가능한 주소의 범위는
0x00000000 ~ 0xFFFFFFFF 이고 한 페이지의 크기를 편의상 0x1000 로 잡으면
페이지 테이블의 개수는 0x100000000 / 0x1000 = 0x100000 개가 되고, 각각이
물리 주소를 저장하고 있어야 하니 0x100000 * 4 = 0x400000 (4MB) 의 크기가 됩니다.

여기서 테이블의 index 가 곧 가상 주소를 의미합니다. 정확히는 페이지 테이블의
index * 0x1000 이 가상 주소의 시작이고, 해당 index 에 저장된 값이 대응하는 물리 주소가 됩니다.
당연히 이 페이지 테이블은 프로세스마다 별개로 하나씩 가지고 있어야 합니다.
애초에 페이지 테이블이 바로 "가상 주소 공간" 이라는 추상적인 개념을 정의하는 그 자체입니다.
(실제로 이렇지는 않습니다. 다단계 페이징도 없고 완전히 단순화해서 생각한 것입니다.)

가령 테이블이 아래와 같다고 가정합니다.

------------------
| 0 | 0x4000 |
| 1 | 0x100000 |
| 2 | 0x9000 |
| ... |
| 120 | 0x3000 |
| 121 | 0x0 |
| ... |
| 150 | 0x1000 |
------------------

A 라는 프로세스가 위와 같은 페이지 테이블을 가지고 있습니다.
이 프로세스 내에서 mov byte ptr [0x50], 0x1 과 같은 명령어를
실행하려고 하면, 0x50 / 0x1000 = 0 이므로 테이블의 0번째에 있는
물리 주소(0x4000)를 들고 와서, 해당 물리 주소에 쓰든 읽든 하게 됩니다.

그럼 만약 어떤 OS 가 4GB 의 가상 주소 공간을 반으로 쪼개서, 2GB 의
유저 영역과 나머지 2GB 의 커널 영역으로 나눠서 사용한다고 가정합니다.

x86 에서 CR3 라는 컨트롤 레지스터가 있는데, 이게 실제로 동작하는 건
생략하고 이것도 위와 마찬가지로 동작을 단순화해서 생각해 보겠습니다.

x86 의 다른 범용 레지스터들처럼, CR3 레지스터 역시 32bit(4byte) 의 크기입니다.
이 값은 물리 주소인데 현재 프로세스(사실 이 때는 Task 라고 해야 합니다)의
페이지 테이블이 저장된 물리 주소 값을 가지고 있습니다.

즉 위에서 적은 것과 같은 4MB 크기의 페이지 테이블도 물리 메모리 어딘가에
저장이 되어있을 것이고, Task 마다 각각 다른 테이블을 사용해야 하는데
CPU 가 명령어를 실행할 때 먼저 CR3 레지스터를 참조해서 해당 물리 주소에
접근해서 페이지 테이블을 참조하게 됩니다. 물론 이 과정은 CPU 가 알아서 합니다.

즉 프로세스라는 개념을 구현하기 위해서, 특히 프로세스의 가상 주소 공간을
구현하기 위해서는 기본적으로 두 가지가 일단 있어야 합니다.

1. 해당 프로세스의 페이지 테이블
2. 페이지 테이블이 위치하는 물리 주소

그리고 만약 현재 프로세스를 다른 프로세스로 스위칭하려면, CR3 레지스터를
스위칭할 대상 프로세스의 페이지 테이블이 위치하는 물리 주소값으로 설정하고
다른 Context 들도 복구해주면 됩니다. 이렇게 되면 그 시점부터 실행하는
모든 명령어들이 새로 설정한 페이지 테이블을 참조해서 동작하게 되겠지요.

2GB 의 유저 영역과 2GB 의 커널 영역이라는 것도 별 게 아닙니다. 그냥 모든 프로세스의
페이지 테이블의 0x80000 번째(0x80000000 / 0x1000) 부터 마지막까지의 값들이
전부 동일하다는 얘기입니다. 커널 코드나 기타 데이터들도 당연히 물리 메모리
어딘가에 저장되어 있는데, 모든 프로세스의 페이지 테이블이 0x80000 ~ 마지막
부분은 동일한 물리 주소를 가지고 있다는 얘기입니다. 그렇게 되면 현재 Task 가
어떤 것이든 간에 커널 영역은 항상 동일하게 보이겠죠.
(물론 유저 모드/커널 모드에 따라 접근 가능 여부를 결정하는 bit 같은 게 일일이
저장되어 있고, 이를 이용해서 유저 모드에서 커널 영역은 접근 불가능하도록 합니다.)

예전 리눅스 커널을 예로 들면 x64 에서 init_level4_pgt 같은 식으로 커널 영역
초기 페이지 테이블을 갖고 이를 프로세스마다 공유(라고 하면 애매할 수도)하는 것이죠.
뭐 위에서 다단계 페이징을 설명 안 했으니 좀 뜬금 없으실 거 같긴 합니다만.

그리고 위에서 계속 "커널 프로세스" 라고 지칭을 하시는데, 운영체제 내에서
실행중인 임의의 유저 프로세스는 당연히 페이지 테이블을 하나 가지고 있습니다.
그렇지만 커널은 그런 독립된 개체가 아니고, 페이지 테이블을 따로 갖고 있지 않습니다.

커널 코드가 동작하는 경우는 유저 프로세스가 시스템 콜을 호출했을 때라든가,
아니면 인터럽트가 발생했을 때 등입니다. 이 두 상황에, CR3 레지스터의 값은 보통
변경되지 않습니다. 한 마디로 A 프로세스에서 syscall 을 호출해서 커널 모드로
전환되었을 때와, B 프로세스에서 syscall 을 호출해서 커널 모드로 전환되었을 때
이 두 경우 커널 코드 실행시의 페이지 테이블은 syscall 을 호출한 프로세스의 원래
페이지 테이블 그대로입니다. 무슨 커널 고유의 페이지 테이블 같은 걸로 바뀌는 게 아닙니다.
같은 커널 코드를 실행해도, 그 시점의 가상 주소 공간에서 유저 영역은
직전의 프로세스마다 별개입니다. 커널을 애초에 그렇게 떼놓고 생각하시면 안 됩니다.
(물론 커널 코드 실행 중엔 얼마든지 다른 페이지 테이블로 변경하고 할 수는 있습니다.)

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.