| 커널 메모리 관리
[정리]
1. 주제
- ㅇ
2. 전체 내용 요약
1) 커널에서 메모리 할당 하는 방법
2) buddy system 확인
3) slab allocator
4) vmalloc 할당 및 회수 과정
[1) 커널에서 메모리 할당 하는 방법]
총 4가지(?) 더 있을 것이며, 조사 필요
alloc_pages : 페이지 자체 할당
kmem_cache_alloc : 자주쓰는 자료구조 할당 ex) task_struct
kmalloc : 요청된 사이즈만큼 물리연속된 메모리 영역 할당. 메모리에 선 할당되어 있는 영역에서 할당을 해주기 떄문에 연속적으로 할당 할 수 있음
vmalloc : 요청된 사이즈만큼 물리적으로 연속된 메모리 영역 할당 보장 할 수 없음. 후매핑용 영역에서 할당하기 때문에 물리적으로 연속됨을 보장 할 수 없는 것임
다른 함수들에서 page가 필요한 경우 __get_free_pages를 호출하게 됨. 이때 몇개의 page가 필요한지 order를 통해 전달하게 되며, alloc_pages() 함수를 통해 실제 page관리 주체인 struct page를 할당 받게 됨.
이후 struct page는 page_address()내에서 virtual address를 return하며 상위 함수에서 요청한 page를 전달하게 됨
struct page는 kernel에서 page를 관리하는 주체를 말하는 것이며, page내 vitrual 변수가 실제 메모리에 할당된 page를 가르키게 됨
즉, _get_free_pages()는 데이터를 로딩할 가상 메모리 주소를 받게 된 것이며, alloc_pages()는 실제 가상 메모리를 관리할 struct page를 할당 받게 된 것임
# cd /sys/kernel/debug/tracing
# echo 1 > events/kmem/mm_page_alloc/enable
# echo 1 > options/stacktrace
# cat trace_pipe
alloc_pages 수행시 orders를 통해 몇 page를 할당 했는지 확인 가능함. order =0 1page 할당
order에 대한 내용은 뒤에서 buddy system에서 좀 더 자세하게 나옴
[2) buddy system 확인]
<buddy info 보기 쉬운 python 스크립트>
#!/usr/bin/env python
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 textwidth=79 autoindent
"""
Python source code
Last modified: 15 Feb 2014 - 13:38
Last author: lmwangi at gmail com
Displays the available memory fragments
by querying /proc/buddyinfo
Example:
# python buddyinfo.py
"""
import optparse
import os
import re
from collections import defaultdict
import logging
class Logger:
def __init__(self, log_level):
self.log_level = log_level
def get_formatter(self):
return logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
def get_handler(self):
return logging.StreamHandler()
def get_logger(self):
"""Returns a Logger instance for the specified module_name"""
logger = logging.getLogger('main')
logger.setLevel(self.log_level)
log_handler = self.get_handler()
log_handler.setFormatter(self.get_formatter())
logger.addHandler(log_handler)
return logger
class BuddyInfo(object):
"""BuddyInfo DAO"""
def __init__(self, logger):
super(BuddyInfo, self).__init__()
self.log = logger
self.buddyinfo = self.load_buddyinfo()
def parse_line(self, line):
line = line.strip()
#self.log.debug("Parsing line: %s" % line)
parsed_line = re.match("Node\s+(?P<numa_node>\d+).*zone\s+(?P<zone>\w+)\s+(?P<nr_free>.*)", line).groupdict()
#self.log.debug("Parsed line: %s" % parsed_line)
return parsed_line
def read_buddyinfo(self):
buddyhash = defaultdict(list)
buddyinfo = open("/proc/buddyinfo").readlines()
for line in map(self.parse_line, buddyinfo):
numa_node = int(line["numa_node"])
zone = line["zone"]
free_fragments = map(int, line["nr_free"].split())
max_order = len(free_fragments)
fragment_sizes = self.get_order_sizes(max_order)
usage_in_bytes = [block[0] * block[1] for block in zip(free_fragments, fragment_sizes)]
buddyhash[numa_node].append({
"zone": zone,
"nr_free": free_fragments,
"sz_fragment": fragment_sizes,
"usage": usage_in_bytes })
return buddyhash
def load_buddyinfo(self):
buddyhash = self.read_buddyinfo()
#self.log.info(buddyhash)
return buddyhash
def page_size(self):
return os.sysconf("SC_PAGE_SIZE")
def get_order_sizes(self, max_order):
return [self.page_size() * 2**order for order in range(0, max_order)]
def __str__(self):
ret_string = ""
width = 20
for node in self.buddyinfo:
ret_string += "Node: %s\n" % node
for zoneinfo in self.buddyinfo.get(node):
ret_string += " Zone: %s\n" % zoneinfo.get("zone")
ret_string += " Free KiB in zone: %.2f\n" % (sum(zoneinfo.get("usage")) / (1024.0))
ret_string += '\t{0:{align}{width}} {1:{align}{width}} {2:{align}{width}}\n'.format(
"Fragment size", "Free fragments", "Total available KiB",
width=width,
align="<")
for idx in range(len(zoneinfo.get("sz_fragment"))):
ret_string += '\t{order:{align}{width}} {nr:{align}{width}} {usage:{align}{width}}\n'.format(
width=width,
align="<",
order = zoneinfo.get("sz_fragment")[idx],
nr = zoneinfo.get("nr_free")[idx],
usage = zoneinfo.get("usage")[idx] / 1024.0)
return ret_string
def main():
"""Main function. Called when this file is a shell script"""
usage = "usage: %prog [options]"
parser = optparse.OptionParser(usage)
parser.add_option("-s", "--size", dest="size", choices=["B","K","M"],
action="store", type="choice", help="Return results in bytes, kib, mib")
(options, args) = parser.parse_args()
logger = Logger(logging.DEBUG).get_logger()
#logger.info("Starting....")
#logger.info("Parsed options: %s" % options)
#print logger
buddy = BuddyInfo(logger)
print buddy
if __name__ == '__main__':
main()
$ cat /proc/buddyinfo
위의 코드 결과들에 대해 이해하기 위해 위의 자료를 보자
기본적으로 처음 시작시 order=10(4MB) 크기 단위로만 page 묶음이 존재하며,
추후 task에 의해 order 10보다 작은 크기의 page가 요청될 경우 order 10의 메모리는 분할된 후 요청된 크기에 맞게 메모리를 할당하게 됨
여기서 처음 조각을 지속적으로 분할해 사용한다고 해서 rmqueue라는 명칭을 사용함
분할 ex) 2^10 -> 2^9 * 2 형태로 분할
회수 ex) 이웃한 2^9 * 2 -> 2^10
분할 <-> 회수 의 과정이 지속적으로 반복 됨
$ vim ${linux}/mm/page_allo.c
struct page *rmqueue()
struct page *__rmqueue_smallest()
<rmqueue를 통해 page 분할 후 할당 하는 부분에 대해 실습이 이루어 지지 않음, 추가적으로 진행 필요>
# cd /sys/kernel/debug/tracing
$ echo 1 > events/kmem/mm_page_free/enable
$ echo 1 > options/stacktrace
$ cat trace
$ vim ${linux}/mm/page_allo.c
page 해제는 free_pages() - > __free_one_page() 최종적으로 버디에 기반 메모리 해제 작업 수행됨
위 함수를 거쳐서 최종적으로 버디시스템 기반 메모리 해지작업 수행