도전2022

Glibc 2 하우투 학습하기. 본문

작업/리눅스

Glibc 2 하우투 학습하기.

hotdigi 2010. 3. 11. 11:04


Chapter 4. The installation of glibc itself

Now we come to the most important thing: the glibc install.


4.1. Obtaining and compiling the source

There are several versions of glibc available, but not in all cases are new versions really better than the old ones. The best thing you can do to find out which works and which ones you should not use is to check out the different forums on the Internet. If you have someone to ask, talk to him about the topic. Maybe he already has installed the new version and might be able to tell you that version x.y.z is a PITA, but version a.b.c works really well!

I decided to install glibc-2.2.4, as I was told it works well, but it is your decision which one to choose.

Ok, now let's go to work:

  1. Get the source from ftp.gnu.org/gnu/glibc/; as I said, I used version 2.2.4.

  2. Unpack the source:

    tar -xzvf glibc-2.2.4.tar.gz

  3. In addition, you will need a package called "linuxthreads," found in the linuxthreads directory on ftp.gnu.org. The file is called:

    glibc-linuxthreads-2.2.4.tar.gz
    Make sure you get the version that corresponds to your glibc source tree.

  4. Copy the linuxthreads package to your glibc source directory:

    cp glibc-linuxthreads-2.2.4.tar.gz glibc-2.2.4

  5. Change to the glibc directory:

    cd glibc-2.2.4

  6. Unpack linuxthreads:

    tar xzvf linux-threads-2.2.4.tar.gz

  7. Configure the package:

    ./configure --enable-add-ons=linuxthreads
    This will configure the package in such a way that the linuxthreads are included in the compile; this is necessary for compliance with other Linux systems. For example, programs you compile will probably not run on another machine if you forget to include this package.

  8. Afterwards, start the compilation of glibc:

    make
    This may take some time (about half an hour on my Duron XP, running with 1.5 GHz).

Now that the library is compiled, everything is ready for the installation, but things are not as easy this time.


4.2. The installation

To install glibc you need a system with nothing running on it, since many processes (for example sendmail) always try to use the library and therefore block the files from being replaced. Therefore we need a "naked" system, running nothing except the things we absolutely need. You can achieve this by passing the boot option

init=/bin/bash
to your kernel. Depending on your bootloader you may need to do different things. In the following I will explain this using the two most common bootloaders, LILO (LInux-LOader) and GNU grub, as examples.


4.2.1. LILO

To start the "only-basics" system, reboot your computer and at the LILO prompt enter the kernel image-name you like to load and append

init=/bin/bash
to it before pressing Return. If you are planing to replace your glibc more often, it might be a good idea to add a separate configuration to your/etc/lilo.conf. For details, refer to the man-page of LILO.


4.2.2. Grub

Grub is a newer bootloader, with enhanced support for different operating systems and and file system types (eg. it supports booting from reiserfs partitions). If you would like to know more go to: http://www.gnu.org/software/grub/, where you will find all the stuff you need.

If you already have Grub installed, you probably use the text-based front-end to select the kernel you prefer to boot. Grub has a nice feature—instead of returning to doing everything by hand, you can simply select your entry and type e, which will bring up an option menu. In this menu you will see the commands Grub executes prior to booting the kernel. Simply select the line saying

kernel="/where/your-kernel-is and-options-are"
and press e again. Now you can edit this line. Here you just add
init=/bin/bash
and after pressing Return to make the changes take effect, press b to start booting.


4.3. After the kernel is booted...

...You will find yourself in an absolute minimal bash-environment.

You will not even be asked to enter your username or password! At this time you are the ultimate super-user; no-one can get around you because the system is in single-user mode, so be careful what you are doing. There are no file-rights set or anything else!

Your prompt will probably look like this:

init-x.y#
At this moment your root (/) has been mounted as read-only, thus you will not be able to write the new library to your hard drive. To make it r/w, enter the command:
mount -o remount,rw /
If your source is located on another partition you must also mount it, as it is not done for you (for me this means mounting my raid system):
mount -t reiserfs /dev/md0 /usr/src
As you see, I defined the file system type, which is needed because mount does not look anything up in /etc/fstab.

Now you can go to the directory containing the source and type in:

make install

If you like, now might be a good time to pray that everything works out fine... ;-)

If everything went through properly, you will return to your prompt after the installation without any error message. In all other cases, please seeChapter 5.

If everything goes fine, run:

ldconfig -v
to update your library cache.

Congratulations! The library is successfully installed. Now type in: mount -o remount,ro / to ensure that all the data is written to the hard drive.

Now start the reboot:

exit
This will cause an error message saying that you have caused a kernel-panic. If possible, restart the computer by using CTRL-ALT-DEL, otherwise use your hardware's reset switch.

Try booting your normal kernel. If everything turns out fine, you are ready to use the new library.



http://wiki.kldp.org/HOWTO/html/Glibc2/Glibc2-HOWTO.html#toc2

이문서는 웹에서 크롤링 한것이다. 
학습한 내용을 정리하여 크롤링하고, 
학습하면서 생기는 질문을 적고, 고찰해보기 위해서 작성한 문서이다. 
(나의 실수가 타인에게 도움이 되었으면 하여 작성함.)

glibc를 arm target 보드에 업그레이드하면서 실수를 하여서 시스템을 여러번 날려버렸다.ㅠ.ㅠ.


리눅스 쪽은 아직 공부해야 할 것이 넘 많다. 





다음 이전 차례

Glibc 2 하우투

Eric Green, thrytis@imaxx.net

v1.2, 26 October 1997

번역: 모름


Glibc 2 하우투는 리눅스 시스템에서 GNU C Library version 2(libc 6)를 설치하고 사용하는 방법을 다룬다.

1. 소개(Introduction)

2. 설치하는 방법을 선택하자.

3. library를 얻기

4. 시험삼아 설치하기

5. Primary C library로 설치하기

6. non-primary libc로 컴파일하기

7. C++ 프로그램을 컴파일하기

8. 버그 보고하기

9. 샘플 specs 파일

10. Miscellanea


다음 이전 차례

1. 소개(Introduction)

1.1 glibc 2란?

Glibc 2은 GNU C Library의 최신 버전이며, 현재 GNU Hurd 시스템, 리눅스 i386, m68k, and alpha 시스템에서 실행된다. 그리고 리눅스 PowerPC, MIPS, Sparc로 포팅작업은 현재 개발 중이다. 앞으로 계획은 다른 architectures와 운영체계에 대한 지원여부를 첨가하는 것이다.^^


(나는 arm 보드에 glibc를 업그레이드 하고자 한다.)


리눅스에서는 glibc 2가 Linux libc 5의 다음 버전인 libc major version 6 으로 사용되고 있다. 이러한 과정은 Linux libc 개발자들의 의도이기도 했다. 현재는 실험적이지만 glibc 2에 관심있는 사람들이 평가하고 사용하는데에는 충분히 안정성이 있다. 만약 당신이 사용하는 프로그램이 glibc 2를 지원한다면 최신 버전은 상당히 안정하다. 버전 2.1은 main stream use에 대한 버전이 될 것이다.


(현재 libc.so.6에 libc-2.10.1.so를 살펴보고자 한다.)


Glibc에서는 printf() 함수와 같은 기본적인 기능의 함수와 로우레벨 네트워크와 같은 시스템 콜 함수 혹은 POSIX 함수(유닉스 응용 프로그램들 간에 이식 가능 운영 체제 인터페이스)들도 포함하고 있다. 


glibc 2에서 유용하는 3가지 추가 옵션(optional add-ons)이 있다.

Crypt

UFC-crypt 패키지이다. Export restriction때문에 분리시켰다.

LinuxThreads

An implementation of the Posix 1003.1c "pthread" interface.

Locale data

Contains the data needed to build the locale data files to use the internationalization features of the glibc.

1.2 이 문서에 대해서

이 하우투 문서는 존재하는 리눅스 시스템에서 glibc 2 library를 설치하는 것을 다룰려고 한다. 현재 libc 5를 사용하는 Intel 기반의 시스템의 사용자를 위해서 적었다. 그러나 다른 시스템을 사용하는 사람이나 glibc 1과 같은 변경된 라이브러리를 사용하는 사람도 적절한 위치에 있는 적합한 파일이름과 architecture 이름으로 바꾸는 작업만으로 이 문서의 정보를 사용할 수 있다.

이 하우투 문서의 최신 판은 Linux Documentation Project 의 일부로 얻을 수 있거나, http://www.imaxx.net/~thrytis/glibc/Glibc2-HOWTO.html 위 위치에서 얻을 수 있다.

1.3 이 문서에서 최근 변경된 내용

버전 1.1과 1.2와 다른 점:

  • Added some information to the Reporting bugs section, and updated bug reporting email and gnats addresses. (Changes by Andreas Jaeger)
  • Updated Credits section.

버전 1.0과 1.1와 다른 점:

  • Updated glibc version to 2.0.5c.
  • Added a reference to the Debian libc5 to libc6 하우투 and Frodo Looijaard's Installing glibc-2 on Linux document.


다음 이전 차례




2. 설치하는 방법을 선택하자.

glibc를 설치하는 방법은 많지 않다. 당신도 시험삼아 라이브러리를 설치할수 있다. 즉 기본적으로 이미 설치되어 있는 라이브러리를 사용하여서 glibc 2를 설치한 후, 프로그램을 컴파일 할 때, 다른 옵션을 사용하면 새로운 라이브러리(glibc 2)를 이용할 수 있다. 물론 이런 방법으로 설치하는 것은 설치 후 glibc를 제거하는 것은 쉽다.-- 주의할 것은 glibc와 링킹되어서 만들어진 프로그램은 더 이상 실행되지 않는다는 것이다.-- glibc를 실험삼아 사용할려면 소스(source)를 가지로 라이브러리를 컴파일하면 된다. 이런 설치 방법은 시험삼아 설치하기에서 자세히 설명했다.

또 다른 설치 방법은 주 라이브러리로 glibc를 사용하기 위한 방법이다. 이것은 당신이 프로그램을 컴파일하면, 새로운 프로그램들은 주로 glibc에 링킹이 된다. 그러나 컴파일 시, 다른 옵션을 사용하면 구 라이브러리에 링킹시킬 수 있다. 바이너리 형태로 라이브러리를 설치할 수 있으며, 또는 스스로 라이브러리를 컴파일 할 수 있다. 만약 최적화 옵션이나 구성 옵션을 변경시키고 싶거나 아니면 바이너리 패키지에서 제공되지 않는 add-on을 사용하려고 한다면, 소스 배포판을 구한 후, 컴파일 하야한다. 이런 설치 과정은 primary C library로 설치하기에서 설명했다.

Frodo Looijaard은 glibc를 설치할 수 있는 다른 방법도 설명했다. 즉, glibc를 secondary libary로 설치하고, glibc를 사용하여서 컴파일되게 cross compiler를 설정하는 방법이다. 이 방법에 대한 설치 과정은 다소 복잡하지만, glibc로 링킹할 때는 쉬운 컴파일을 할 수 있다.(??) 이 방법은 그의 site에 있는 Installing glibc-2 on Linux 문서에 설명되어있다.

만약 현재 Debian 1.3을 사용 중이고, glibc를 사용하는 데비안의 unstable version 으로 업그레이드를 하기 싫다면, Debian libc5 to libc6 Mini-HOWTO을 참고하여서 데비안 패키지를 업그레이드를 시킬 수 있다.

glibc 2를 중요한 시스템에 설치하려고 한다면, 주 라이브러리로 설치하지 말라. 시험삼아 설치하거나, 더 좋은 것은 여유있는 시스템에 설치해서 테스트해 보아라. 설령 버그가 없다하더라도, 어떤 프로그램은 컴파일 하기 전에 변경될 필요가 있는데, 이것은 함수의 prototypes과 types이 변경되기 때문이다.


다음 이전 차례

다음 이전 차례



3. library를 얻기

glibc 2 는 glibc 패키지와 3개의 추가된 옵선 패키지(LinuxThreads, Locale, Crypt)로 구성되어 있다. 소스는 다음 위치에서 찾을 수 있다.

ftp://prep.ai.mit.edu/pub/gnu/glibc/ 에 과거에서 부터 현재까지의 파일이 등록되어 있다. 

glibc-2.11.1.tar.bz2 14.9 MB 10. 12. 29. 오후 4:00:00
glibc-2.11.1.tar.bz2.sig 198 B 10. 12. 29. 오후 4:00:00
glibc-2.11.1.tar.gz 20 MB 10. 12. 29. 오후 3:58:00
glibc-2.11.1.tar.gz.sig 198 B 10. 12. 29. 오후 3:58:00
glibc-2.11.1.tar.xz 9.6 MB 10. 12. 29. 오후 4:01:00
glibc-2.11.1.tar.xz.sig 198 B 10. 12. 29. 오후 4:01:00
glibc-2.11.tar.bz2 15.0 MB 10. 11. 3. 오후 8:33:00
glibc-2.11.tar.bz2.sig 65 B 10. 11. 3. 오후 8:33:00
glibc-2.11.tar.gz 20 MB 10. 11. 3. 오후 8:33:00
glibc-2.11.tar.gz.sig 65 B 10. 11. 3. 오후 8:33:00
glibc-2.11.tar.xz 9.7 MB 10. 11. 3. 오후 8:34:00
glibc-2.11.tar.xz.sig ...... ... 


glibc-2.10.1.tar.gz 를 설치하여 보자. 


버전 2.0.5c은 추가적으로 패치가 필요하는 데, 이것은 다음 위치에서 찾을 수 있다.

ftp://prep.ai.mit.edu/pub/gnu/glibc-2.0.5-2.0.5c.diff.gz.

완전한 컴파일과 설치에 필요한 디스크 공간은 약 150 MB가 필요하다. 주요 라이브러리 패키지만의 기본적인 바이너리 설치시에는 약 50 MB가 필요하다.

버전 2.0.5c에 대한 바이너리 패키지는 구할수 없지만, 버전 2.0.5 바이너리 패키지는 i386, alpha, 68k용으로 배포되며, 다음 위치에서 얻을 수 있다.

crypt add-on에 대해서는 export restrictions이 있어서, non-US 사용자는 다음 위치에서 crypt add-on 패키지를 얻을 수 있다.ftp://ftp.ifi.uio.no/pub/gnu.

Red Hat 배포판을 사용하는 사람은 glibc 2용 rpm을 다음 위치에서 얻을 수 있다. ftp://ftp.redhat.com/pub/redhat/tbird/RedHat/RPMS/ 신 베파 배포판인 Red Hat 4.8은 glibc 2가 주 C 라이브러리로 사용되었다.

데비안 배포판을 사용하는 사람은 glibc 2의 패키지를 다음 위치에서 얻을 수 있다. ftp://ftp.debian.org/debian/unstable/binary-i386/devel/,ftp://ftp.debian.org/debian/unstable/binary-m68k/devel/, ftp://ftp.debian.org/debian/unstable/binary-alpha/devel/. 파일은 libc6으로 이름이 되었다. Glibc 2는 데비안의 hamm 버전에서는 기본 패키지의 일부이며, 데비안 2.0부터는 주 libc가 될 것이다.


ftp를 접속하여서도 가지고 올수도 있다. 

- ftp ftp.gnu.org
- cd pub/gnu/glibc
- hash
- bin
- get glibc-x.x.x.tar.bz2
- quit



다음 이전 차례

다음 이전 차례



4. 시험삼아 설치하기

Glibc 2를 시험삼아 설치하면, 컴파일 할 때에 특별한 변수를 사용하여 glibc 2에 링킹시키지 않는한 이미 설치되어 있는 기존의 라이브러리에 링킹이 된다. 설치 디렉토리 위치(path)가 몇 파일에 포함되어 컴파일되므로 , 소스로 부터 라이브러리를 설치해야만 한다.

4.1 컴파일하기와 설치하기

전제 조건

  • About 150 MB free disk space
  • GNU make 3.75
  • gcc >= 2.7.2 (better 2.7.2.1)
  • binutils 2.8.1 (for alpha you need a snapshot
  • bash-2.0
  • autoconf-2.12 (if you change configure.in)

RAM 64 MB인 i586@133 컴퓨터에서 추가 라이브러리(add-ons)포함한 전체 라이브러리 를 컴파일하는데 소요 시간은 약 3시간이며, i686@2에 컴퓨터의 경우는 약 1시간 30분 정도 걸렸다.

소스를 추출하기

소스 파일을 컴파일하기 위해서는 Archives에서 소스 파일을 추출해야만 한다. 아래와 같이 하면 된다:

 tar xzf glibc-2.0.5.tar.gz cd glibc-2.0.5 cat ../glibc-2.0.5-2.0.5c.diff.gz | gzip -d | patch -p0 tar xzf ../glibc-linuxthreads-2.0.5.tar.gz tar xzf ../glibc-crypt-2.0.5.tar.gz tar xzf ../glibc-localedata-2.0.5.tar.gz 
위의 방법처럼 하면, glibc-2.0.5 디렉토리에 linuxthreads, crypt, localeddata 디렉토리가 생성된다. 이렇게 해야지만 configure가 추가된 라이브러리 디렉토리를 찾을 수 있다.

Configure를 실행하기

glibc-2.0.5 디렉토리에서 "compile"이란 디렉토리를 만들고, cd로 'compile' 디렉토리로 온다. 모든 작업은 이 compile 디렉토리에서 이루어진다.

 mkdir compile cd compile 

위의 과정을 마쳤으면, '../configure'을 실행시키면 된다. 추가 패키지를 사용하고 싶으면, 추가 패키지에 대한 옵션(--enable-add-ons <예> --enable-add-ons=linuxthreads,crypt,localedata)을 포함시켜야 한다. 또 설치하고 싶은 디렉토리를 선택해야 한다. 보통 설치 디렉토리로 '/usr/i486-linuxglibc2'을 사용한다. 다음은 configure line의 예이다:

 ../configure --enable-add-ons=linuxthreads,crypt,localedata --prefix=/usr/i486-linuxglibc2 

컴파일하기와 설치하기

컴파일하고 제대로 되었는지 확인하는 절차는 다음과 같이 한다:

 make make check 

'make check'가 성공적으로 끝났으면, 라이브러리를 설치한다:

 make install 

4.2 동적로더(dynamic loader)를 업그레이드하자.

  1. 만들어진 ld.so를 /lib/ld-linux.so.2로 링킹시킨다:
     ln -s /usr/i486-linuxglibc2/lib/ld-linux.so.2 /lib/ld-linux.so.2 
    This is the only library where the location is fixed once a program is linked, and using a link in /lib will ease upgrading to glibc as your primary C library when the stable version is released.


  2. /etc/ld.so.conf 파일을 편집한다. 파일의 마지막 라인에 새롭게 만들어진 라이브러리 디렉토리의 경로명(path)를 적으면 된다. 즉 우리가 설치했던 경우에는 '/usr/i486-linuxglibc2/lib'을 적으면 된다. (주의 사항 : '은 적는 것이 아님.^^) /etc/ld.so.conf 파일을 변경했으면, 다음과 같이 실행해 보라.
     ldconfig -v 

4.3 gcc를 configure하기

설치의 마지막 단계가 /usr/lib/gcc-lib를 업데이트(update)하는 것이다. 이것을 해야지만 gcc가 어떻게 새로운 라이브러리를 사용할지를 알 수 있다. 우선 있는 configuration파일을 복사한다. 현재 configuration의 내용을 알기 위해서는 다음과 같이 하면 된다:

 % gcc -v Reading specs from /usr/lib/gcc-lib/i486-unknown-linux/2.7.2.2/specs gcc version 2.7.2.2 
이 경우는 시스템이 i486-unknown-linux, 2.7.2.2가 버전이다. 당신이 해야할 일은 /usr/lib/gcc-lib/<system> 을 새로운 테스트 시스템 디렉토리로 복사하는 것이다: 복사하는 것이다.:
 cd /usr/lib/gcc-lib/ cp -r i486-unknown-linux i486-linuxglibc2 
그 다음 다음과 같이 하라.
 cd /usr/lib/gcc-lib/i486-linuxglibc2/2.7.2.2 
이 디렉토리에서 'specs'파일을 편집한다. 즉 specs 파일내에 /lib/ld-linux.so.1을 /lib/ld-linux.so.2로 바꾼다. 또 %{...:-lgmon} 가 들어간 모든 문장을 지워라. 왜냐면 glibc는 profile하기 위해서 gmon 라이브러리를 사용하지 않기 때문이다. 샘플 specs 파일은 샘플 specs 파일단원 에서 찾을 수 있다.

4.4 헤더 파일 링크를 업데이트하기.

헤더 파일 링크는 다음과 같이 한다:

 cd /usr/i486-linuxglibc2/include ln -s /usr/src/linux/include/linux ln -s /usr/src/linux/include/asm ln -s /usr/X11R6/include/X11 
You might also have other libraries such as ncurses which need their header files put in this directory. You should copy or link the files from /usr/include. (Some libraries may need to be recompiled with glibc2 in order to work with it. In these cases, just compile and install the package to /usr/i486-linuxglibc2.)

4.5 설치된것을 테스트하기

우선 다음과 같은 파일(glibc.c)를 만든다:

 #include <stdio.h> main() {     printf("hello world!\n"); } 
그리고 옵션"-b <base install directory> -nostdinc -I<install directory>/include -I/usr/lib/gcc-lib/<new system dir>/<gcc version>/include" 을 가지고 컴파일 한다.
 % gcc -b i486-linuxglibc2 -nostdinc -I/usr/i486-linuxglibc2/include -I/usr/lib/gcc-lib/i486-linuxglibc2/2.7.2.2/include glibc.c -o glibc 
ldd를 이용하여서 프로그램이 glibc2에 링크되었는지 확인해본다:
 % ldd glibc libc.so.6 => /usr/i486-linuxglibc2/lib/libc-2.0.5.so (0x4000d000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) 
컴파일이 되고, 링크 과정도 정상이며, 실행시 "hello world!" 가 출력이 된다면, 설치는 성공적으로 끝난 것이다. (축하....^^)


다음 이전 차례

다음 이전 차례



5. Primary C library로 설치하기

Glibc 2를 주 C 라이브로리로 설치하는 방법을 설명하겠다. 주 C 라이브러리로 설치 한다는 것은 당신이 컴파일하는 프로그램을 glibc로 링크되게 하는 것이다. 물론, 다른 버전에 링크시킬려면 컴파일 옵션을 추가시켜서 컴파일하면 된다.

만약 당신이 레드헷이나 데비안을 사용하고 적절한 rpm 파일이나 deb 파일을 다운했었다면, 레드헷이나 데비안의 설치 안내를 읽어 보아라. 그러면, 여기있는 내용을 읽지 않아도 된다.

5.1 소스로부터 라이브러리를 설치하기

여기에서는 소스로부터 glibc 2와 add-ons를 컴파일하는 방법을 설명하려고 한다. 최적화 옵션이나 구성 옵션을 변경하고 싶거나 바이너리에 없는 패키지를 사용하려고 할 때 라이브러리를 컴파일할 필요가 있다.

전제 조건

  • About 150 MB free disk space
  • GNU make 3.75
  • gcc >= 2.7.2 (better 2.7.2.1)
  • binutils 2.8.1 (for alpha you need a snapshot)
  • bash-2.0
  • autoconf-2.12 (if you change configure.in)

RAM 64 MB인 i586@133 컴퓨터에서 추가 라이브러리(add-ons)포함한 전체 라이브러리 를 컴파일하는데 소요 시간은 약 3시간이며, i686@2에 컴퓨터의 경우는 약 1시간 30분 정도 걸렸다.

소스를 추출하기

소스 파일을 컴파일하기 위해서는 Archives에서 소스 파일을 추출해야만 한다. 아래와 같이 하면 된다:

 tar xzf glibc-2.0.5.tar.gz cd glibc-2.0.5 cat ../glibc-2.0.5-2.0.5c.diff.gz | gzip -d | patch -p0 tar xzf ../glibc-linuxthreads-2.0.5.tar.gz tar xzf ../glibc-crypt-2.0.5.tar.gz tar xzf ../glibc-localedata-2.0.5.tar.gz 
위의 방법처럼 하면, glibc-2.0.5 디렉토리에 linuxthreads, crypt, localeddata 디렉토리가 생성된다. 이렇게 해야지만 configure가 추가된 라이브러리 디렉토리를 찾을 수 있다.

Configure를 실행하기

glibc-2.0.5 디렉토리에서 "compile"이란 디렉토리를 만들고, cd로 'compile' 디렉토리로 온다. 모든 작업은 이 compile 디렉토리에서 이루어진다.

mkdir compilecd compile

위의 과정을 마쳤으면, '../configure'을 실행시키면 된다. 추가 패키지를 사용하고 싶으면, 추가 패키지에 대한 옵션(--enable-add-ons <예> --enable-add-ons=linuxthreads,crypt,localedata)을 포함시켜야 한다. 또 설치하고 싶은 디렉토리를 선택해야 한다. 보통 설치 디렉토리로 '--prefix=/usr'을 사용한다. 다음은 configure line의 예이다:

 ../configure --enable-add-ons=linuxthreads,crypt,localedata --prefix=/usr

컴파일하기

컴파일하고 제대로 되었는지 확인하는 절차는 다음과 같이 한다:

 make make check 

5.2 설치하기 위한 준비 작업하기

소스로부터 설치했든, 바이너리 파일로 설치했든 glibc 2라이브러리 설치 준비하기 위해선 어떤 파일을 옮길 필요가 있다. 이유는 앞으로 컴파일 할 새로운 프로그램을 glibc에 링크시키고, 정적으로 링크되지 않는 구 프로그램은 여전히 libc 5에 의존 하게 하기위해서이다. 고로 구 버전에 덮어 쓰지 않아야한다.

  1. 구 파일(libc 5)을 저장할 새로운 디렉토리를 만든다:
     mkdir -p /usr/i486-linuxlibc5/lib 
  2. 구(libc 5용) 헤더 파일을 /usr/include 에서 다른 디렉토리로 옮긴다:
     mv /usr/include /usr/i486-linuxlibc5/include 
  3. 새로운(glibc 2용) include 디렉토리를 만들고, 다른 include에 링크시킨다:
     mkdir /usr/include ln -s /usr/src/linux/include/linux /usr/include/linux ln -s /usr/src/linux/include/asm /usr/include/asm ln -s /usr/X11R6/include/X11 /usr/include/X11 ln -s /usr/lib/g++-include /usr/include/g++ 
    링크는 배포판에 따라서 약간 다를수 있다. 적어도 슬렉웨어의 경우 /usr/local/g++-include에 g++ 헤더파일이 있고, 반면에 데비안 패키지 경우 /usr/include/g++에 g++헤더파일이 있고, /usr/lib/g++-include는 /usr/include/g++ 에 링크되어 있다. 후자의 경우, 아마 당신은 원래 g++ include 디렉토리를 /usr/include에 옮기길 원할 것이다.
  4. 다른 헤더 파일을 복원하고 링크한다. 어떤 비표준 라이브러리경우, 예로 ncureses, /usr/include에 헤더파일을 놓거나 /usr/include 밑에 있는 그들의 include 디렉토리에 링크를 한다. 이 파일들이나 링크는 다른 라이브러리를 적절히 사용될수 있게 복원을 해 둘 필요가 있다.
  5. /etc/ld.so.conf파일 맨위에 glibc 2용 라이브러리 디렉토리(예, / usr/i486-linuxlibr/lib)를 추가한다. glibc를 설치할 때 이상한 메세지를 피하기 위해서는 ld.so-1.8.8이상의 버전을 설치되어 있어야 한다.
  6. 구 C 라이브러리를 새로운 디렉토리(/usr/i486-linuxlibc5/lib)로 옮기거나 복사한다.
     mv /usr/lib/libbsd.a /usr/i486-linuxlibc5/lib mv /usr/lib/libc.a /usr/i486-linuxlibc5/lib mv /usr/lib/libgmon.a /usr/i486-linuxlibc5/lib mv /usr/lib/libm.a /usr/i486-linuxlibc5/lib mv /usr/lib/libmcheck.a /usr/i486-linuxlibc5/lib mv /usr/lib/libc.so /usr/i486-linuxlibc5/lib mv /usr/lib/libm.so /usr/i486-linuxlibc5/lib cp /lib/libm.so.5.* /usr/i486-linuxlibc5/lib cp /lib/libc.so.5.* /usr/i486-linuxlibc5/lib 
    만약 /usr이 /와 다른 파티션에 있다면, libm.so.5와 libc.so.5는 move가 아닌 복사되어야 한다. 이유는 리눅스를 시작하는데 필요한 프로그램이 libm.so.5와 libc.so.5를 사용하기때문이고 이것들은 루트 디렉토리에 있어야만 한다.
  7. /usr/lib/*.o 퍄일을 새로운 디렉토리로 옮긴다.
     mv /usr/lib/crt1.o /usr/i486-linuxlibc5/lib mv /usr/lib/crti.o /usr/i486-linuxlibc5/lib mv /usr/lib/crtn.o /usr/i486-linuxlibc5/lib mv /usr/lib/gcrt1.o /usr/i486-linuxlibc5/lib 
  8. 당신의 라이브러리를 옮겼으면 라이브러리 cache를 업데이트한다.
     ldconfig -v 

5.3 바이너리 패키지로부터 설치하기

미리 컴파일된 바이너리로부터 glibc를 설치하고자 하면, 다음과 같이 한다:

 cd / gzip -dc glibc-2.0.bin.i386.tar.gz | tar tvvf - gzip -dc glibc-crypt-2.0.bin.i386.tar.gz | tar tvvf - ldconfig -v 
다른 architecture나 다른 버전을 가지고 있다면, 적절한 파일 이름으로 바꾸면 된다.

5.4 소스로부터 설치하기

소스로부터 라이브러리를 설치하려면, 다음을 실행하라:

 make install ldconfig -v 

5.5 gcc specs 을 업데이트하기

바이너리로부터 설치이든 소스로부터 설치하든 마지막 단계는 gcc specs 파일을 업데이트하는 것이다. 그래야만 당신 프로그램이 적절하게 링크될 수 있다. gcc가 사용하는 specs 파일을 알아내기 위해서는 다음과 같이 한다:

 % gcc -v reading specs from /usr/lib/gcc-lib/i486-unknown-linux/2.7.2.2/specs gcc version 2.7.2.2 

이 경우는 i486-unknown-linux 시스템이고 버전이 2.7.2.2이다. 당신이 해야할 일은 /usr/lib/gcc-lib/<system>을 구 시스템 디렉토리로 복사하는 것이다:

 cd /usr/lib/gcc-lib/ cp -r i486-unknown-linux i486-linuxlibc5 

그 다음 다음과 같이 하라.

 cd /usr/lib/gcc-lib/i486-unknown-linux/2.7.2.2 

이 디렉토리에서 'specs'파일을 편집한다. 즉 specs 파일내에 /lib/ld-linux.so.1을 /lib/ld-linux.so.2로 바꾼다. 또 %{...:-lgmon} 가 들어간 모든 문장을 지워라. 왜냐면 glibc는 profile하기 위해서 gmon 라이브러리를 사용하지 않기 때문이다. 샘플 specs 파일은 샘플 specs 파일단원에서 찾을 수 있다.

5.6 설치된것을 테스트하기

우선 다음과 같은 파일(glibc.c)를 만든다:

 #include <stdio.h> main() {     printf("hello world!\n"); } 
그리고 컴파일한다.
 % gcc glibc.c -o glibc 
ldd를 이용하여서 프로그램이 glibc2에 링크되었는지 확인해본다:
 % ldd glibc libc.so.6 => /lib/libc.so.6 (0x4000e000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) 
컴파일이 되고, 링크 과정도 정상이며, 실행시 "hello world!" 가 출력이 된다면, 설치는 성공적으로 끝난 것이다. (축하....^^)


다음 이전 차례

다음 이전 차례



6. non-primary libc로 컴파일하기

프로그램을 컴파일 하다 보면, 다른 라이브러리를 사용하고 싶은 때가 있을 것이다. 이 단원에서는 어떻게 하면 다른 라이브러리를 사용할 수 있는가를 설명한다. 우선 기억해야 할 것은 앞의 두 단원에서 예로 사용되었던 디렉토리 이름과 설치 이름을 사용한다는 것이다. Remember to change the names to fit your setup.

6.1 non-primary libcs 사용할 때 경고

시스템 부팅 과정에 사용되는 프로그램을 컴파일 할 때 기억할 것은, 그 프로그램을 동적으로 링크하고 non-root 파티션이 마운트되기 이전에 사용한다면, 모든 링크되는 라이브러리는 root 파티션에 있어야만 한다는 것이다. glibc를 주 C 라이브러리로 설치하는 과정(앞 단원)을 했다면, 구 libc는 /lib에 있다. /lib는 root 파티션에 있어야한다. 이것은 아직도 모든 당신 프로그램이 부팅하는 동안에는 작동한다는 것이다. 그러나, 만약 /usr이 다른 파티션에 있고, glibc를 /usr/i486-linuxglibc2에 연습용으로 설치했다면, glibc와 함께 컴파일된 프로그램들은 /usr 파티션이 마운트되기 이전까지는 작동하지 않는다는 것이다.

6.2 연습용 glibc와 함께 컴파일하기.

연습용 glibc와 함께 프로그램을 컴파일하기위해선 glibc include에 include path 를 다시 설정할 필요가 있다.

Specifying "-nostdinc" will negate the normal paths, and "-I/usr/i486-linuxglibc2/include" will point to the glibc includes. You will also need to specify the gcc includes, which are found in /usr/lib/gcc-lib/i486-linuxglibc2/2.7.2.2/include (assuming you installed the test lib in i486-linuxglibc2 with gcc version 2.7.2.2).

To link a program with a test-install glibc, you need to specify the gcc setup. This is done by using the option "-b i486-linuxglibc2".

For most programs, you can specify these new options by adding them to the CFLAGS and LDFLAGS makefile options:

 CFLAGS = -nostdinc -I/usr/i486-linuxglibc2/include -I/usr/lib/gcc-lib/i486-linuxglibc2/2.7.2.2/include -b i486-linuxglibc2 LDFLAGS = -b i486-linuxglibc2 
If you are using a configure script, define the CFLAGS and LDFLAGS shell variables (by using env/setenv for csh/tcsh, or set/export for sh/bash/etc) before running configure. The makefiles generated by this should contain the proper CFLAGS and LDFLAGS. Not all configure scripts will pick up the variables, so you should check after running configure and edit the makefiles by hand if necessary.

If the programs you are compiling only call gcc (and not cpp or binutils directly), you can use the following script to save having to specify all of the options each time:

 #!/bin/bash /usr/bin/gcc -b i486-linuxglibc2 -nostdinc \              -I/usr/i486-linuxglibc2/include \              -I/usr/lib/gcc-lib/i486-linuxglibc2/2.7.2.2/include "$@" 
You can then use this script instead of "gcc" when compiling.

6.3 Compiling programs with libc 5 when glibc is primary library

To compile a program with your old libraries when you have installed glibc as your main library, you need to reset the include paths to the old includes. Specifying "-nostdinc" will negate the normal paths, and "-I/usr/i486-linuxlibc5/include" will point to the glibc includes. You must also specify "-I/usr/lib/gcc-lib/i486-linuxlibc5/2.7.2.2/include" to include the gcc specific includes. Remember to adjust these paths based on the what you named the new directories and your gcc version.

To link a program with your old libc, you need to specify the gcc setup. This is done by using the option "-b i486-linuxlibc5".

For most programs, you can specify these new options by appending them to the CFLAGS and LDFLAGS makefile options:

 CFLAGS = -nostdinc -I/usr/i486-linuxlibc5/include -I/usr/lib/gcc-lib/i486-linuxlibc5/2.7.2.2/include -b i486-linuxlibc5 LDFLAGS = -b i486-linuxlibc5 
If you are using a configure script, define the CFLAGS and LDFLAGS shell variables (by using env/setenv for csh/tcsh, or set/export for sh/bash/etc) before running configure. The makefiles generated by this should contain the proper CFLAGS and LDFLAGS. Not all configure scripts will pick up the variables, so you should check after running configure and edit the makefiles by hand if necessary.

If the programs you are compiling only call gcc (and not cpp or binutils directly), you can use the following script to save having to specify all of the options each time:

 #!/bin/bash /usr/bin/gcc -b i486-linuxlibc5 -nostdinc \              -I/usr/i486-linuxlibc5/include \              -I/usr/lib/gcc-lib/i486-linuxlibc5/2.7.2.2/include "$@" 
You can then use this script instead of "gcc" when compiling.


다음 이전 차례

다음 이전 차례



7. C++ 프로그램을 컴파일하기

libg++는 수학 라이브러리의 일부를 사용하므로 libm을 링크시켜야한다. 기존의 libg++는 구 라이브러리와 함께 컴파되었기때문에, glibc와 다시 컴파일 하거나 아니면 바이너리를 얻어야한다. glibc에 링크된 바이너리 libg++는 다음 위치에서 찾을 수 있다. ftp://ftp.yggdrasil.com/private/hjl/.

7.1 연습용 glibc 설치의 경우에 대한 libg++설치하기

연습용으로 glibc를 설치했다면, libg++파일을 glibc가 설치되었던 디렉토리에 설치해야한다. (앞의 단원대로 했다면, /usr/i486-linuxglibc2에 libg++파일을 설치한다.) 바이너리 패키지로부터 설치하고자 한다면, 임시 디렉토리에서 파일내용을 추출한 다음 usr/lib/파일들을 <install directory>/lib/ 디렉토리로 옮기고, usr/include/파일들을 <install directory>/include/ 디렉토리로 옮긴다. (주의 사항: include/g++ 링크를 먼저 지워라), 그리고 usr/bin/파일을 <install directory>/bin/ 디렉토리로 옮긴다.

7.2 주 glibc 설치의 경우에 대한 libg++ 설치하기

주 라이브러리로 glibc를 설치했다면, 먼저 구 libg++ 파일들을 구 libc 디렉토리로 옮겨라. (g++프로그램을 구 libc 와 컴파일하고 싶다면,...) 이것을 하는데 가장 쉬운 방법은 libc 5로 컴파일된 libg++의 새로운 복사판을 설치하고(앞의 단원에서 설명했다), 정상적으로 glibc 버전을 설치한다.

7.3 non-primary libc로 C++프로그램 컴파일하기

C++ 프로그램을 non-primary libc와 컴파일하고 싶으면, g++ include 디렉토리를 포함시켜야한다. (즉 연습용 설치 경우, /usr/i486-linuxglibc2/include/g++이고, 주 glibc 설치 경우는 /usr/i486-linuxlibc5/include/g++이다) 보통 CXXFLAGS 변수를 첨가하는 방법으로 사용할 수 있다.

 CXXFLAGS = -nostdinc -I/usr/i486-linuxglibc2/include -I/usr/lib/gcc-lib/i486-linuxglibc2/2.7.2.2/include -I/usr/i486-linuxlibc5/include/g++ -b i486-linuxglibc2 


다음 이전 차례

다음 이전 차례



8. 버그 보고하기

lib에 버그가 있다고 생각되면, 우선 FAQ를 읽어보라. 다른 사람들이 가진 문제점과 같은 경우라면 해결책이 있다. 또는 INSTALL 파일에 있는 "Recommended Tools to Install the GNU C Library" 단원을 체크해 보아라. 이유는 어떤 버그는 glibc의 버그가 아닌 tools의 버그이기때문이다.

일단 버그를 발견했으면, 그것이 진짜 버그인지 확인하라. GNU C 라이브러리가 다른 C 라이브러리와 같은 방법으로 작동하는지 확인하면 된다. 만약 다른 C라이브러 리와 같은 방법으로 작동하지 않으면 라이브러리에 이상이 있는 것이다.

다음은 http://www-gnats.gnu.org:8080/cgi-bin/wwwgnats.pl로 접속한 후, 버그 데이타베이스를 검토해 보라. 이미 보고되었던 문제인지 확인해라. libc와 함께 배포되는 BUGS 파일도 보아라.

새로운 버그라고 생각이 되면, 그 문제가 일어날수 있는 경우를 좁힐 수 있을 만큼 좁혀라.(?) C 라이브러리 경우 한 라이브러리 함수 호출로 줄여서 보고할수 있으면 그렇게 해라. 이것이 너우 어렵지 않아야 한다.

간단한 테스트 판을 가지고 있다면 버그를 보고하라. 다음은 버그 보고할 때 포함해야 할 내용이다. (1) 버그가 생기는 test case와 결과들. (2) 만약 버그 원인에 대한 생각이 있으면, 당신의 생각 (3) 당신이 사용했던 시스템 종류, GNU C 라이브러리 버전,GCC CC 컴파일러 버전, GNU Binutils 버전 (4) 'configure'를 실행시켰을 때 만들어지는 'config.status'와 'config.make'의 파일

GNU C 라이브러리에 대한 버그 레포트는 glibcbug 쉘 스크립트를 사용해서 bugs@gnu.org 로 보내라. (다른 주소는 bugs@gnu.ai.mit.edu) 또는http://www-gnats.gnu.org:8080/cgi-bin/wwwgnats.pl에 있는 GNATS 웹 인터페이스를 통해서 제출할 수 있다.

제안과 질문은 bugs-glibc@prep.ai.mit.edu에 있는 메일링 리스트로 보내라. 만약 gnewsgroup인 gnu.bug.glibc를 읽어보지 않았다면, bug-glibc-request@prep.ai.mit.edu에 물어서 리스트에 제출할 수 있다.

<bug-gcc@prep.ai.mit.edu> 로 GNU C 라이브러리에 대한 버그 보고서를 보내지 마라. 그곳은 GNU CC에 대한 버그 보고서를 위한 곳이다. GNU CC와 GNU C 라이브러리는 다른 사람들에 의해서 관리된다.


다음 이전 차례

다음 이전 차례



9. 샘플 specs 파일

여기에 있는 샘플 specs파일은 glibc 2용이며 컴파일할 때, 링크할 때 gcc에 의해서 사용되는 파일이다. 이 파일은 /usr/lib/gcc-lib/<new system dir>/<gcc version>에 있다. 만약 x86 시스템을 사용한다면, 이 샘플 specs 파일을 복사할 수 있다.

 *asm: %{V} %{v:%{!V:-V}} %{Qy:} %{!Qn:-Qy} %{n} %{T} %{Ym,*} %{Yd,*} %{Wa,*:%*} *asm_final: %{pipe:-} *cpp: %{fPIC:-D__PIC__ -D__pic__} %{fpic:-D__PIC__ -D__pic__} %{!m386:-D__i486__} %{posix:-D_POSIX_SOURCE} %{pthread:-D_REENTRANT} *cc1: %{profile:-p} *cc1plus: *endfile: %{!shared:crtend.o%s} %{shared:crtendS.o%s} crtn.o%s *link: -m elf_i386 %{shared:-shared}   %{!shared:     %{!ibcs:       %{!static:       %{rdynamic:-export-dynamic}     %{!dynamic-linker:-dynamic-linker /lib/ld-linux.so.2}}  %{static:-static}}} *lib: %{!shared: %{pthread:-lpthread}        %{profile:-lc_p} %{!profile: -lc}} *libgcc: -lgcc *startfile: %{!shared:      %{pg:gcrt1.o%s} %{!pg:%{p:gcrt1.o%s}                  %{!p:%{profile:gcrt1.o%s}                         %{!profile:crt1.o%s}}}}    crti.o%s %{!shared:crtbegin.o%s} %{shared:crtbeginS.o%s} *switches_need_spaces: *signed_char: %{funsigned-char:-D__CHAR_UNSIGNED__} *predefines: -D__ELF__ -Dunix -Di386 -Dlinux -Asystem(unix) -Asystem(posix) -Acpu(i386) -Amachine(i386) *cross_compile: 0 *multilib: . ; 


다음 이전 차례

다음 이전 차례



10. Miscellanea

10.1 Further information

Web Pages

Newgroups

10.2 Credits

Most of this information was stolen from the GNU Libc web page and from Ulrich Drepper's <drepper@gnu.ai.mit.edu> glibc 2 announcement and his comments. Andreas Jaeger <aj@arthur.rhein-neckar.de> provided some of the Reporting bugs section.

The following people have provided information and feedback for this document:

  • Mark Brown <M.A.Brown-4@sms.ed.ac.uk>
  • Ulrich Drepper <drepper@gnu.ai.mit.edu>
  • Scott K. Ellis <ellis@valueweb.net>
  • Aron Griffis <agriffis@coat.com>
  • Andreas Jaeger <aj@arthur.rhein-neckar.de>
  • Frodo Looijaard <frodol@dds.nl>
  • Ryan McGuire <rmcguire@freenet.columbus.oh.us>
  • Shaya Potter <spotter@capaccess.org>
  • Les Schaffer <godzilla@futuris.net>
  • Andy Sewell <puck@pookhill.demon.co.uk>
  • Stephane <sr@adb.fr>
  • Jan Vandenbos <jan@imaxx.net>

Translations of this document are being done by:

  • French: Olivier Tharan <tharan@int-evry.fr>
  • Japanese: Kazuyuki Okamoto <ikko-@pacific.rim.or.jp>

10.3 Feedback

Besides writing this HOWTO, maintaining the glibc 2 for Linux page, and using it on my machine, I have nothing to do with the glibc project. I am far from knowledgeable on this topic, though I try to help with problems mailed to me. I welcome any feedback, corrections, or suggestions you have to offer. Please send them to thrytis@imaxx.net.


다음 이전 차례



2010.3.11 학습 하면서 조금 추가함. 









glibc 학습하자.


 [glibc] 동적 메모리 관리 (1)
glibc: 2.10.1
arch: x86


이번에는 GNU C library (이하 glibc)에서 동적 메모리를 관리하는 방식에 대해서 살펴볼 것이다.
(여기서 설명하는 내용은 32비트 머신 환경에 해당하며 64비트 환경의 경우 차이가 있을 수 있다.)

glibc 내에 포함된 동적 메모리 할당자 (malloc) 모듈은
Doug Lea가 최초로 작성한 구현(이름의 앞자를 따서 dlmalloc이라고 부른다)을
Wolfram Gloger가 UNIX multi-thread 환경을 고려하여 수정한 ptmalloc2를 기반으로 작성되었다.

동적 메모리로 할당되는 영역(chunk)은 내부적으로 해당 chunk에 대한 metadata를 저장하기 위한
공간을 포함하는데 가장 중요한 정보는 해당 chunk의 크기이다.
malloc() 호출 시에는 원하는 영역의 크기를 지정하지만 free() 호출 시에는 단순히 포인터 만을 넘기는 것에서 알 수 있듯이
malloc()으로 (물론 calloc/realloc 등도 동일하다) 할당된 영역 어딘가에는 크기 정보가 포함되어 있다.

실제로 malloc() 호출 시에는 실제로 필요한 영역 + 크기를 저장하기 위한 헤더까지 포함한 크기의
chunk가 할당되며 따라서 (32bit) x86 아키텍처에서는 (최소) 4 바이트 만큼의 공간이 더 필요하다.
각 chunk는 8바이트 단위로 정렬(align)되기 때문에 실제 크기는 좀 더 커질 수 있다.

또한 free memory에 해당하는 chunk를 관리하기 위한 doubly linked list와
물리적으로 인접한 이전(prev) chunk를 병합할 때 사용하는 필드까지 포함하면
할당될 수 있는 최소 chunk의 크기는 16바이트이다.
(이 경우 user가 사용 가능한 영역의 최대 크기는 12바이트가 된다.)

실제로 사용되는 malloc_chunk 구조체는 다음과 같이 정의되어 있다.

struct malloc_chunk {
  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */

  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;

  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

malloc_chunk 자료 구조가 사용되는 방식은 약간 혼동스럽기 때문에 아래의 그림을 참조하면 좋을 것이다.
malloc_chunk가 사용자에게 할당되면 오직 size 필드 만의 의미있는 값을 가진다. (할당된 chunk의 크기)
다른 영역은 무시되며 해당 메모리 영역이 free() 되었을 때 의미있는 정보가 기록된다.


제일 처음에 나오는 prev_size 필드는 해당 chunk 바로 앞에 위치한 이전 chunk의 크기이다.
(당연한 얘기이지만 여기서 말하는 이전 chunk란 list로 연결된 chunk가 아니라
물리적으로 연속된 주소에 위치한 chunk를 말한다.)
이 필드는 이전 chunk가 free() 되었을 때 설정되며
이를 통해 이전 chunk의 헤더 위치를 손쉽게 찾을 수 있기 때문에
chunk를 통합하는 경우에 유용하게 사용될 수 있다.

다음은 size 필드로 현재 chunk의 크기를 나타내며 malloc() 시에 설정된다.
앞서 말한대로 각 chunk는 8바이트 단위로 정렬되므로 하위 3비트는 특별한 용도로 사용한다.
따라서 실제 chunk의 크기를 구하려면 하위 3비트를 무시해야 한다.

그림에서 P 플래그는 이전 chunk가 사용 중인지 여부를 나타내는 것이다. (PREV_INUSE)
즉 이 플래그가 지워져 있으면 이전 chunk는 free chunk라는 의미가 된다.
M 플래그는 해당 필드가 mmap() 시스템 콜을 통해 할당된 것인지를 나타낸다. (IS_MMAPPED)
나중에 살펴보겠지만 mmap()으로 할당된 chunk는 약간 다른 방식으로 관리된다.
N 플래그는 multi-thread application에서 각 thread마다 다른 heap 영역을 사용하는 경우
현재 chunk가 main heap (arena)에 속하는지 여부를 나타낸다. (NON_MAIN_ARENA)

#define PREV_INUSE       0x1
#define IS_MMAPPED       0x2
#define NON_MAIN_ARENA   0x4

#define SIZE_BITS        (PREV_INUSE|IS_MMAPPED|NON_MAIN_ARENA)
#define chunksize(p)     ((p)->size & ~(SIZE_BITS))

다음에 나오는 필드들은 모두 free chunk에서 사용하는 것이며
malloc()으로 할당되었을 때는 단순히 무시되고 사용자 데이터를 위한 공간으로 사용된다.
fd와 bk는 각각 forward, backward pointer를 의미하는 것이며
fd_nextsize와 bk_nextsize도 동일하지만 상대적으로 큰 크기의 chunk에서만 사용된다.

특이한 것은 다음 chunk의 prev_size 필드도 현재 chunk의 데이터 영역으로 사용된다는 것이다.
사실 개념적으로는 이 부분도 현재 chunk에 속하며, (그림에서 <actual chunk> 부분)
free() 시에는 현재 chunk의 크기를 (중복하여) 저장하는 용도로 사용된다. (단 플래그는 제외)
(이를 boundary tag 기법이라고 한다.)
또한 free() 시에는 다음 chunk의 PREV_INUSE 비트 (P 플래그)를 지워야 하며
prev_size 필드는 오직 P 플래그가 지워진 경우에만 사용되어야 한다.

이렇게 할당된 chunk들은 이후에 free()를 통해 해지되면 bin이라는 구조를 통해 관리된다.
bin은 사실 각 chunk의 fd, bk 필드로 연결된 doubly-linked list일 뿐이며
chunk의 크기에 따라 총 126개로 분리된다. (첫 번째 bin은 특별한 용도로 사용된다.)

이는 또 small bin과 large bin으로 나누어 지는데
small bin은 chunk 크기 기준으로 512 바이트 미만인 것들이 8 바이트 단위로 구분되는데
최소 크기인 16 바이트부터, 24, 32, 48, ..., 504 바이트 까지 총 62개의 bin으로 구성된다.

개념적으로 bin의 구성은 다음과 같다. (코드 내의 주석에서 발췌)
총 127 개 중에서 제일 처음 bin은 특별한 용도로 사용되므로 126개 만 사용된다.
또한 small bin에 속하는 8 바이트 단위의 bin이 64개라고 표현되어 있으나
실제로는 (chunk의 최소 크기 제한으로 인해) 0과 8에 해당하는 첫 2개가 존재하지 않으므로
위에서 말한대로 62개만 사용된다. 아래의 데이터는 단지 개념적으로 구조를 이해하기 위한 것이다. 

    64 bins of size       8
    32 bins of size      64
    16 bins of size     512
     8 bins of size    4096
     4 bins of size   32768
     2 bins of size  262144
     1 bin  of size what's left

512 바이트 이상의 크기를 가지는 chunk를 위한 large bin은
small bin처럼 동일한 크기의 chunk만을 포함하는 것이 아니라
해당 index가 나타내는 크기보다 작은 크기의 chunk들은 모두 포함한다.
즉, 4KB를 위한 bin이 있다면 이는 정확히 4096 바이트 크기의 chunk 만을 포함하는 것이 아니라
4088, 4000, 3968 등의 크기를 가지는 chunk들도 포함한다는 것을 뜻한다.
다만 이들은 할당의 효율성을 위해 해당 bin 내에서 크기 별로 정렬(sort)된다.

이 때 fd_nextsize와 bk_nextsize 필드가 이용되며
이들은 현재 bin 내에서 크기가 다른 첫 번째 chunk에 대한 포인터를 저장한다.

이러한 bin들은 해당 bin 내에 이용 가능한 free chunk가 있는지 빨리 조사하기 위해
별도의 bitmap을 유지하여 관리한다. 해당 bin 내에 free chunk가 없다면
그 보다 큰 bin 내의 가장 작은 chunk를 빨리 찾기 위해 이를 이용할 수 있다.

위에서 말한대로 첫 번째 bin은 unsorted chunk의 list로 사용된다.
이는 일종의 cache와 같은 것으로 일단 free() 된 chunk는 곧바로 해당 bin으로 들어가지 않고
먼저 unsorted chunk list에 들어가며 이 후의 메모리 할당 시
동일한 크기의 영역을 다시 요청하는 경우에는 이를 바로 재사용하도록 한다.
이는 FIFO (queue)와 같은 방식으로 동작하며, 일단 검색된 chunk는 바로 할당(재사용)되거나
아니면 원래의 bin으로 돌아가게 된다. 즉, 단 한 번의 재사용 기회 만이 주어진다.

또한 small bin에 속하는 chunk 중에서 (기본값) 72 바이트 이하의 크기를 가지는 chunk는
fast bin이라고하는 또 다른 cache를 통해 관리되는데,
이는 malloc() 및 free() 시에 가장 먼저 조사하는 bin으로써
속도를 높이기 위해 single-linked list로 구성되며 LIFO (stack)와 같은 방식으로 동작한다.
fast bin 내의 chunk들은 unsorted bin과 달리 (특정한 조건에 의해) 병합(consolidation)이 일어나지 않는 한
계속 bin 내에 남아서 요청을 수행할 수 있다.

이상의 여러 자료 구조들을 그림을 나타내면 대략 다음과 같다.
(그림에는 편의를 위해 약간의 오류가 숨어있다. 대강 의미만 파악하자..;;)


그 밖에 (기본값) 128KB 이상의 큰 메모리를 요청하는 경우에는
heap을 이용하지 않고 mmap() 시스템 콜을 통해 별도의 메모리 영역을 할당하여
chunk를 만들고 이를 사용자에게 반환하며, 이러한 chunk들은 bin 내에 속하지 않는다.
이러한 chunk들은 IS_MMAPPED 플래그로 쉽게 확인할 수 있기 때문에
free() 시에 단순히 munmap()을 호출하여 메모리 영역을 해지한다.

또한 모든 chunk의 맨 마지막에는 top chunk가 존재하는데
top chunk는 어떠한 bin에도 속하지 않으며 heap 영역의 마지막에 위치한다.
다른 free chunk들이 메모리 할당 요청을 만족하지 못하는 경우에만
top chunk를 분할하여 요청을 처리하며, 현재 top chunk 크기로도 처리할 수 없는 경우에는
sbrk() 함수를 통해 heap 영역을 확장하여 top chunk의 크기를 늘린후 요청을 처리한다.

=== 참고 문헌 ===

이 글과 관련있는 글을 자동검색한 결과입니다 [?]

 [glibc] 동적 메모리 관리 (2)
glibc: 2.10.1
arch: x86

이전 글 보기: 

이번에는 malloc() 함수의 실행 과정을 따라가 보자.
malloc() 함수의 서비스 루틴은 public_mALLOc() 함수이며
실제로는 전처리 과정에 의해 __libc_malloc()이라는 이름으로 바뀐다.
(__malloc과 malloc은 이 함수에 대한 alias이다.)

public_mALLOc() 함수는 다음과 같은 작업을 수행한다.
  1. __malloc_hook이 정의되어 있다면 해당 hook을 호출한 후 종료한다.
  2. 그렇지 않으면 malloc을 처리할 heap 영역(arena)를 찾는데 일반적으로 main_arena가 사용된다.
  3. arena에 대한 lock을 건 후에 실제 malloc의 처리 루틴인 _int_malloc() 내부 함수를 호출한다. (아래 참조)
  4. 만약 _int_malloc() 함수가 NULL을 반환했다면 다른 arena에 대해 _int_malloc()을 다시 한 번 호출한다.
  5. arena에 걸린 lock을 해제한다.
  6. _int_malloc() 함수가 반환한 값을 반환하고 종료한다.

_int_malloc() 함수는 다음과 같은 작업을 수행한다.
  1. 요청한 크기를 chunk 크기에 맞춘다. 즉, 헤더를 위한 4 바이트를 더한 후 8 바이트 단위로 정렬(align)한다. 이 후로는 chunk 크기를 기준으로 계산한다.
  2. 주어진 크기가 fast bin에 속한다면 (<= 72) fast bin 내의 free chunk를 찾아본다.
    1.  주어진 크기에 맞는 fast bin의 인덱스를 계산한다.
    2.  해당 인덱스의 포인터가 가리키는 chunk를 victim 지역 변수에 저장한다.
    3.  victim이 NULL이 아니라면 fast bin의 해당 인덱스에 victim->fb가 가리키는 chunk를 저장하고 victim의 데이터 영역에 대한 포인터를 반환한다. (종료)
  3. 주어진 크기가 small bin에 속한다면 (< 512) small bin 내에서 free chunk를 찾아본다.
    1.  주어진 크기에 맞는 small bin의 인덱스를 계산하여 idx 지역 변수에 저장한다.
    2.  해당 인덱스 내에 가장 오래된 chunk를 victim 지역 변수에 저장한다.
    3.  victim이 올바른 chunk를 가리킨다면 해당 인덱스 내의 리스트에서 victim을 제거하고, victim 바로 다음에 위치한 chunk의 헤더에 P (PREV_INUSE) 플래그를 설정한 뒤 victim의 데이터 영역에 대한 포인터를 반환한다. (종료) 설명을 단순하게 하기 위해 앞으로 'victim을 반환한다'라는 표현은 P 플래그를 설정하는 것과 데이터 포인터를 반환하는 작업을 뜻하는 것으로 사용할 것이다.
    4.  victim이 올바른 chunk를 가리키지 않는다는 것은 다음의 두 경우 중 하나이다.
    5.   victim이 NULL이면 최초로 malloc() 함수가 호출된 경우이다. 아직 초기화가 제대로 이루어지지 않았으므로 malloc_init_state() 내부 함수를 호출하여 초기화를 수행한다.
    6.   victim이 NULL이 아니고 bin 자신을 가리킨다면 해당 bin은 비어있는 것이다. (초기화 과정에서 각 bin의 리스트는 자기 자신을 가리키도록 설정된다.)
  4. 여기까지 왔다면 주어진 크기는 large bin에 속한다. large bin은 바로 찾아보지 않고 다음과 같은 준비 과정을 거친다.
    1.  주어진 크기에 맞는 large bin의 인덱스를 계산하여 idx 지역 변수에 저장한다.
    2.  만약 fast bin을 포함하고 있다면 이들을 모두 병합(consolidate)하여 보다 큰 chunk로 만든다. 이는 큰 메모리 요청을 받은 경우에는 더 이상 작은 크기의 요청이 (최소한 당분간은) 없을 것이라고 가정하기 때문이다. (이로 인해 fast bin으로 인한 fragmentation 문제를 줄일 수 있다.)
  5. 이제 unsorted bin을 검색하여 일치하는 크기의 free chunk가 있는지 검색한다.
    1.  unsorted bin 내의 가장 오래된 chunk를 victim 지역 변수에 저장한다.
    2.  victim을 unsorted bin의 리스트에서 분리한다. (unsorted bin 내의 chunk들은 오직 한 번만 검사된다.)
    3.  victim의 크기와 주어진 크기가 일치한다면 victim을 반환한다. (종료)
    4.  만약 victim의 unsorted bin 내의 마지막 chunk이고 주어진 크기가 small bin에 속하는 작은 크기이며 victim이 이전 요청을 처리하고 남은 자투리 영역이고 (last_remainder), victim의 크기가 주어진 크기를 처리하고 다른 chunk를 만들만한 여유가 있다면 victim을 분할하여 요청을 처리하고 나머지는 다시 unsorted bin 내에 남겨둔다. (종료) 이는 작은 크기의 연속된 요청이 메모리 상의 연속된 위치에 존재하도록 하여 locality를 높이기 위함이다.
    5.  victim의 크기에 맞는 bin의 리스트의 제일 처음에 삽입한다.
    6.  만약 victim이 large bin에 속한다면 large bin 내의 다른 chunk들과 크기를 비교하여 적절한 위치에 삽입된다.
  6. 이제 large bin에 속하는 경우 해당 리스트를 검사한다. large bin은 (일정 범위 내에서) 크기가 다른 chunk들이 섞여있으므로 현재의 요청을 만족시킬 수 있는 가장 작은 크기의 chunk를 찾아야 한다.
    1.  앞서 계산한 idx에 해당하는 bin의 제일 앞에 있는 chunk를 victim 지역 변수에 저장한다.
    2.  victim의 크기가 주어진 크기보다 큰지 검사하여 그렇지 않다면 탐색을 중지한다. large bin은 앞쪽부터 큰 chunk가 놓이고 뒤로 갈수록 (즉, fd_nextsize 링크를 따라갈수록) 크기가 작아진다. 현재 검사한 victim의 크기는 해당 bin 내에 있는 가장 큰 chunk의 크기이므로 이보다 큰 요청은 해당 bin에서 처리할 수 없다.
    3.  victim을 victim->bk_nextsize로 설정한다. 이제 victim은 해당 bin 내의 가장 작은 크기의 chunk이다.
    4.  victim의 크기가 주어진 크기보다 커질 때까지 victim을 victim->bk_nextsize로 변경한다.
    5.  이제 요청을 처리할 chunk를 찾았다. victim을 리스트에서 분리한다.
    6.  victim의 크기가 요청을 처리하고도 다른 chunk를 구성할 수 있을 정도로 크다면 분할하여 나머지 영역을 chunk로 만들어서 unsorted bin에 추가한다.
    7.  victim을 반환한다. (종료)
  7. 여기까지 왔다면 해당하는 bin 내에서 적당한 chunk를 찾지 못한 것이다. idx 값을 하나 증가시킨 후 더 큰 크기의 bin 내에 free chunk가 있는지 검사한다. (이는 bitmap을 통해 빨리 확인할 수 있다.)
    1.  현재 인덱스에 해당하는 bitmap을 검사하여 free chunk가 있는지 확인한다. 만약 해당 bin이 비어있다면 인덱스를 하나 증가시킨 후 검사를 다시한다. 모든 bitmap을 검사했다면 8번 과정으로 넘어간다.
    2.  bitmap이 설정된 bin이 있다면 해당 bin 내의 (가장 작은 크기의) 가장 오래된 chunk를 victim 지역 변수에 저장한다.
    3.  victim을 리스트에서 분리한다.
    4.  victim의 크기가 요청을 처리하고도 다른 chunk를 구성할 수 있을 정도로 크다면 분할하여 나머지 영역을 chunk로 만들어서 unsorted bin에 추가한다. 나머지 영역의 크기가 small bin에 속한다면 last_remainder 변수가 나머지 영역을 가리키도록 설정한다.
    5.  victim을 반환한다. (종료)
  8. 여기까지 왔다면 bin 내에 모든 chunk가 비어있거나 요청을 만족할 수 없는 상황이다. 이제 top chunk를 사용한다.
    1.  만약 top chunk의 크기가 주어진 요청을 만족하고도 새로운 chunk를 만들 수 있는 크기라면 분할하여 victim을 반환하고 나머지 영역을 top chunk로 설정한다. (종료)
    2.  그렇지 않고 주어진 크기가 small bin 영역에 속한다면 fast bin이 남아있을 것이다. fast bin을 병합한 후 다시 2번 과정으로 돌아가서 할당을 시도한다.
    3.  그렇지 않다면 시스템의 heap 영역을 늘려야 한다. 이는 sYSMALLOc() 함수가 처리하며, 이 함수의 반환값을 반환하고 종료한다.

sYSMALLOc() 함수는 다음과 같은 작업을 수행한다.
  1. 먼저 요청된 크기가 mmap() 시스템 콜을 이용하도록 설정된 범위에 속하고 (>= 128K) mmap() 사용 횟수 제한을 넘지 않는다면 (< 65536회) mmap()을 호출한다. 호출이 성공하면 chunk에 M (IS_MMAPPED) 플래그를 설정하고 데이터 영역의 포인터를 반환한다. mmap()으로 할당한 chunk는 분할할 수 없으므로 크기에 여유가 있더라도 하나의 chunk로 사용된다.
  2. 그 보다 작은 크기거나 mmap() 호출이 실패했다면 heap 영역을 늘려야 한다. 증가시킬 크기는 요청한 크기에서 원래의 top chunk 크기를 빼고 top chunk가 기본적으로 가져야 할 여유 공간의 크기(pad)를 더한 후 할당 후 남은 영역에 chunk를 구성하기 위한 최소 크기(16)를 더한 값이다. 또한 이는 시스템의 페이지 크기에 맞춰 조정된다.
  3. 위에서 계산한 크기에 대해 sbrk() (MORCORE라는 이름을 사용한다) 시스템 콜을 호출한다.
  4. 호출이 성공했다면 __after_morecore_hook이 정의되어 있는지 검사하여 이를 호출한다.
  5. 호출이 실패했다면 크기와 횟수 제한에 상관없이 mmap() 시스템 콜을 호출하여 메모리 할당을 시도한다. 이것이 성공하면 해당 arena는 더 이상 연속된 주소 공간에 속하지 않으므로 NONCONTIGUOUS_BIT를 설정한다. 실패했다면 errno 변수를 ENOMEM으로 설정하고 NULL을 반환한다. (종료)
  6. 할당된 영역이 chunk 단위로 정렬되었는지 다시 확인하여 필요한 경우 sbrk()를 다시 호출한다.
  7. 이전의 sbrk() 호출이 성공적으로 수행되었다면 top 영역의 크기를 그에 맞게 늘린다.
  8. 그렇지 않다면 메모리 정렬이 맞지 않거나 mmap() 호출을 통해 불연속적인 구간이 할당된 경우이다. 메모리 주소를 정렬하여 다시 한 번 sbrk()를 호출하고 불연속적인 구간의 끝부분에 dummy chunk (fence) 2개를 할당하여 원래의 top chunk가 불연속적인 공간과 consolidate되지 않도록 한다.
  9. 이제 새로 할당된 영역을 분할하여 요청을 처리하고 나머지 영역을 새로운 top chunk로 설정한다.

중요한 과정을 간단하게 (순서대로) 다시 정리해보면 다음과 같다.
  • fast bin에 속한다면 해당 bin 내의 chunk를 반환 (LIFO, exact fit)
  • small bin에 속한다면 해당 bin 내의 chunk를 반환 (FIFO, exact fit)
  • large bin에 속한다면 fast bin을 병합하여 큰 chunk로 만듬
  • unsorted bin을 검사하여 일치하는 chunk를 반환 (FIFO, exact fit)
  • 단 unsorted bin 내에 이전에 분할한 chunk가 있고 요청이 small bin에 속하면 해당 chunk를 다시 분할하여 반환 (next fit)
  • large bin에 속한다면 해당 bin 내에서 요청을 만족하는 가장 작은 chunk를 반환 (FIFO, best fit)
  • bitmap을 검사하여 요청을 만족하는 가장 작고 오래된 chunk를 반환 (FIFO, best fit)
  • 현재 top chunk가 요청을 처리할 수 있다면 분할하여 반환 (exact fit)
  • 충분히 큰 크기의 요청이라면 mmap()을 통해 chunk를 생성 후 반환 (best fit)
  • sbrk()로 top chunk를 늘리거나 mmap()으로 새로운 top chunk를 할당하여 요청한 크기만큼 분할하여 반환 (exact fit)

fast bin 내의 chunk들을 병합하는 일은 malloc_consolidate() 내부 함수가 수행한다.
이 함수는 먼저 동적 메모리 관리자가 제대로 초기화 되었는지 검사하여 필요한 경우 malloc_init_state() 함수를 호출한 후 종료한다.
초기화된 후라면 fast bin 내의 모든 chunk에 대해 인접한 이전 chunk와 다음 chunk가 사용 중인 검사하고
사용 중이 아니라면 이를 하나로 합친다.
이전 chunk와 다음 chunk를 얻는 작업은 다음과 같은 chunk_at_offset() 매크로를 이용하여 간단하게 처리할 수 있다.

/* Treat space at ptr + offset as a chunk */
#define chunk_at_offset(p, s)  ((mchunkptr)(((char*)(p)) + (s)))

이전 chunk는 chunk_at_offset(p, -(p->prev_size)), 다음 chunk는 chunk_at_offset(p, chunksize(p))로 구할 수 있다.
fast bin 내의 chunk들은 free되었어도 아직 해당하는 P (PREV_INUSE) 플래그가 지워지지 않았다.
이제 (병합되지 않은) 다음 chunk에서 해당 P 플래그를 지운다.
이렇게 병합된 chunk는 top chunk로 들어가거나 (top chunk와 인접한 chunk인 경우), unsorted bin으로 들어간다.

calloc() 함수의 구현은 (malloc()과 동일하게) 내부적으로 _int_malloc() 함수를 이용하며
주어진 size와 nmemb 인자를 곱해서 할당할 크기를 결정한 후 _int_malloc()을 호출하고
할당된 메모리를 모두 0으로 채운 후 반환한다.

realloc() 함수는 먼저 현재 chunk에 요청을 처리할 만한 여유 공간이 있거나
바로 다음 chunk가 free chunk이고 이 둘을 합친 크기가 요청을 처리할 수 있다면
둘을 병합하여 반환한다. 그렇지 않으면 _int_malloc() 함수를 호출한 뒤
이전의 메모리 내용을 새로 할당된 영역으로 복사하고 이전 영역을 free한 뒤 새로운 영역을 반환한다.

이제 free()의 경우를 살펴보도록 하자.
free() 함수는 실제로 public_fREe() 함수가 처리하며 실제 이름은 __libc_free()이다.
(마찬가지로 __free, free가 alias로 설정되어 있다.)

public_fREe() 함수는 다음과 같은 작업을 수행한다.
  1. __free_hook이 설정되어 있다면 해당 hook을 호출하고 종료한다.
  2. 주어진 메모리 영역의 포인터로부터 chunk의 포인터를 얻는다.
  3. 해당 chunk가 mmap()으로 할당된 것이라면 munmap()을 호출하여 해당 메모리 영역을 해지한다. (종료)
  4. 그렇지 않다면 해당 chunk가 속한 arena의 포인터를 얻고 lock을 건다.
  5. _int_free() 함수를 호출하여 실제 해지 작업을 수행한다.
  6. arena에 대한 lock을 푼다.

_int_free() 함수는 다음과 같은 작업을 수행한다.
  1. chunk의 헤더 정보를 통해 해당 chunk의 크기를 얻는다.
  2. 주어진 크기가 fast bin에 속한다면 (<= 72) fast bin에 해당 chunk를 삽입한다.
    1.  해당 arena가 fast bin에 chunk를 포함한다고 표시한다.
    2.  주어진 크기에 해당하는 인덱스를 계산하여 fast bin의 포인터를 얻는다.
    3.  포인터에 저장된 chunk가 현재 chunk가 일치한다면 중복해서 free()를 호출한 경우이다. 에러를 반환한다.
    4.  그렇지 않다면 fast bin의 제일 앞에 현재 chunk를 삽입한다. (종료)
  3. 이제 일반적인 해지 작업을 처리하기 전에 몇 가지 기본적인 검사를 수행한다.
  4. 현재 chunk를 인접한 (free) chunk들과 병합시킨다.
    1.  이전 chunk가 free chunk라면 현재 chunk와 병합하고, 병합된 chunk를 현재 chunk로 설정한다.
    2.  다음 chunk가 top chunk라면 현재 chunk를 top chunk로 병합하고 top chunk의 크기를 조정한다.
    3.  다음 chunk가 top chunk가 아니고 free chunk라면 병합한다.
    4.  다음 chunk가 top chunk가 아니고 사용 중인 chunk라면 다음 chunk의 P 플래그를 지운다.
  5. 현재 chunk가 top chunk가 아니라면 unsorted bin에 추가하고 현재 chunk의 size 필드와 다음 chunk의 prev_size 필드에 현재 chunk의 크기를 기록한다.
  6. (병합된) 현재 chunk의 크기가 64K 이상이고 현재 arena가 fast bin을 포함하면 malloc_consolidate()를 호출하여 fast bin을 병합한다.
  7. 현재 chunk의 크기가 정해진 크기 (128K) 이상이면 sYSTRIm() 함수를 호출하여 top chunk의 크기를 줄이려고 시도한다.

sYSTRIm() 함수는 top chunk가 (sbrk()를 통해 확장된) heap 영역에 속할 경우에만 수행되며
현재 top chunk의 크기에서 chunk 정보를 저장하기 위한 최소 크기(16)와
top chunk가 기본적으로 가져야 할 여유 공간의 크기(pad)만큼을 뺀 크기를 페이지 단위로 조정하여 sbrk()를 호출한다.
또한 __after_morecore_hook이 정의되어 있다면 해당 hook을 호출한 뒤 top chunk의 크기를 조정한다.
 by namhyung | 2009/12/26 21:51 | System | 트랙백(2) | 덧글(3)

[glibc] 동적 메모리 관리 (3)
glibc: 2.10.1
arch: x86

이전 글 보기:

이번에는 malloc 모듈 내의 다른 보조 함수들에 대해서 알아보기로 한다.

가장 먼저 살펴볼 함수는 mallopt() 함수이다.
이 함수는 malloc() 시의 동작을 제어하는 다음과 같은 매개 변수들을 조정할 수 있다.
  • M_MXFAST: fast bin의 최대 크기이다. 기본값은 64로 설정되어 있으며 최대 80까지 늘릴 수 있다. (이 크기는 malloc() 시 주어지는 인자에 대한 것이며, 실제 chunk 크기는 이보다 약간 커진다.)
  • M_TRIM_THRESHOLD: free() 호출 시 병합된 chunk의 크기가 이 값보다 커지면 자동으로 sYSTRIm() 함수를 호출한다. 기본값은 128K이다.
  • M_TOP_PAD: top chunk가 기본적으로 유지하는 여유 공간의 크기이다. sYSMALLOc()을 통해 top chunk의 크기를 늘릴 때나 free() 시 sYSTRIm()이 top chunk의 크기를 줄일 때 사용하며, 기본값은 (문서와는 달리) 128K이다.
  • M_MMAP_THRESHOLD: sYSMALLOc()을 통해 시스템에 메모리 할당을 요청할 때 원하는 chunk의 크기가 이 값보다 크다면 sbrk() 대신 mmap()을 이용하여 할당한다. 기본값은 128K이다.
  • M_MMAP_MAX: mmap()을 이용하여 할당할 수 있는 chunk의 최대 개수이다. 기본값은 64K이다.
  • M_CHECK_ACTION: 메모리 할당 오류 시 취할 행동을 결정한다. 이 값이 5이면 간단한 메시지 만을 출력하고 NULL을 반환한다. 그렇지 않고 최하위 (0번) 비트가 설정되어 있으면 자세한 메시지를 출력하고 NULL을 반환한다. 그렇지 않고 1번 비트가 설정되어 있으면 abort()를 호출하여 프로세스를 바로 종료한다. 아무 비트도 설정되지 않았으면 단순히 NULL을 반환한다. 기본값은 3이다.
  • M_PERTURB: 메모리 테스트를 위한 패턴을 지정한다. 이 값이 0이 아니면 새로 할당된 메모리 영역을 모두 이 값을 이용하여 채운다. 기본값은 0이다.

사실 이러한 매개 변수들은 mallopt()로 조정하지 않고도 환경 변수 설정을 통해 바꿀 수 있다. (단 M_MXFAST 제외)
환경 변수의 이름은 해당 매개 변수 이름 앞의 'M_'을 'MALLOC_'으로 바꾸고 뒤에는 '_'를 붙인 형태이다.
즉 다음과 같이 호출하면 된다. (bash 기준)

$ export MALLOC_TOP_PAD_=4096
$ ./a.out

이 매개 변수들의 현재 값을 확인하기 위해서는 malloc_get_state() 함수를 이용할 수 있다.
(하지만) 이 함수의 원형은 공개되어 있지만 이 함수가 반환하는 malloc_save_state 구조체는 공개되어 있지 않다.
실제로 위의 구조체는 glibc/malloc/hooks.c에 정의되어 있으므로 그 내용을 복사해서 사용하면 된다.
일반적으로 이 함수는 이러한 내용을 숨긴채 단순히 현재 동적 메모리 할당자의 상태를 저장해 두었다가
나중에 다시 (malloc_set_state() 함수를 통해) 복구하는 용도로만 사용하는 듯 하다.
참고로 이 함수가 반환한 구조체도 malloc()을 통해 동적으로 할당한 것이므로 사용한 후에는 free()해 주어야 한다.

그 외의 동적 메모리 할당자의 상태 정보는 다음과 같은 함수들을 통해서도 알 수 있다.
malloc_stats() : 대략적인 메모리 할당 통계 정보를 stderr로 출력한다.
mallinfo() : 메모리 할당 통계 및 free chunk들에 대한 정보를 mallinfo 구조체로 반환한다.
(특이하게도 포인터가 아닌 구조체 자체를 반환한다!)
malloc_info() : 메모리 할당 통계 및 free chunk들에 대한 정보를 arena 별로 나누어
인자로 주어진 파일 스트림에 XML 형식으로 기록한다. 또 하나의 인자인 options는 반드시 0이어야 한다.

malloc_trim() 함수는 기존의 M_TOP_PAD 매개 변수를 무시하고
인자로 주어진 pad 값만큼의 여유 공간 만을 확보해두고 top chunk의 나머지 영역을 sbrk()를 통해 시스템에 반환한다.
추가로 large bin 내의 4K 이상의 크기를 가지는 chunk들에 대해서도 MADV_DONTNEED를 인자로 하여 madvise()를 호출해서
사용하지 않는 페이지들을 시스템에 반환한다.

malloc_usable_size() 함수는 현재 할당된 메모리 영역에서 사용할 수 있는 크기가 얼마인지를 알려준다.
메모리 할당 요청이 정렬 제한보다 작은 단위의 크기이거나 정확한 크기의 chunk를 찾지 못한 경우에는
실제로 요청한 크기보다 큰 chunk가 할당되었을 수 있다.
(예를 들어 0바이트의 요청에도 16 바이트 크기의 chunk를 반환한다.)

이 함수의 반환값은 일반 chunk인 경우 다음 chunk의 prev_size 필드(footer)까지 이용할 수 있으므로
chunk 크기에서 (size 필드(header)에 필요한) 4바이트를 뺀 크기이며
mmap()으로 할당한 chunk의 경우에는 다음 chunk가 존재하지 않으므로 chunk 크기에서 8을 뺀 크기이고
free()된 chunk인 경우에는 (당연하게도) 0이다.
주의할 점은 fast bin에 속하는 chunk의 경우 free()된 후에도
곧바로 P 플래그가 지워지지 않기 때문에 malloc_usable_size()는 원래의 크기를 반환한다는 것이다.

이 외에도 할당된 메모리 주소를 원하는 위치로 정렬하여 반환하도록 하는 함수들이 있다.
(정렬은 chunk 시작 주소가 아닌 사용자에게 반환되는 메모리 주소 기준으로 이루어진다.)
memalign() 함수는 POSIX 표준 이전에 정의된 함수로 인자로 지정한 정렬 기준과 크기에 맞는 메모리 영역을 할당하여 반환한다.
valloc() 함수는 memalign()에서 정렬 기준을 시스템의 페이지 크기로 한 것이다.
pvalloc() 함수는 memalign()에서 정렬 기준과 크기를 모두 시스템의 페이지 단위로 맞춘 것이다.
posix_memalign() 함수는 memalign과 비슷하지만 반환되는 포인터의 주소를 인자로 넘기도록 하고 에러 코드를 반환한다.

이들은 내부적으로 요청한 크기보다 더 큰 크기로 _int_malloc() 함수를 호출하여 정렬된 위치에 chunk를 새로 할당하고
나머지 영역은 가능하다면 다시 chunk로 만들어서 _int_free() 함수를 통해 곧바로 해지한다.

마지막으로 기존의 malloc(), free() 등의 기본 동작을 override 할 수 있는 여러 hook들이 존재한다.
이 hook 변수에 원하는 함수를 설정하여 통계 정보나 디버깅 정보 등을 수집하는 용도로 사용할 수 있다.
일반적으로 hook 함수는 원하는 작업을 먼저 수행한 뒤 예전 hook을 복원하고 원래의 함수를 호출한 후에
다시 hook을 설정하는 방식으로 작성한다.
자세한 내용은 __malloc_hook(3) man 페이지나 cinsk님의 강좌를 살펴보면 좋을 것이다.

이 글과 관련있는 글을 자동검색한 결과입니다 [?]

 by namhyung | 2009/12/29 18:48 | System | 트랙백(1) | 덧글(0)

'작업 > 리눅스' 카테고리의 다른 글

linux.die.net/man/  (0) 2010.03.12
시스템 관리자 명령어들  (0) 2010.03.12
라이브러리 학습 하자.  (0) 2010.03.01
readelf -l dir_static  (0) 2010.02.19
리눅스 라이브러리 관련 학습  (0) 2010.02.19