'Linux/Programming'에 해당되는 글 16건

  1. 2008.06.01 [펌]중요 string.h 함수
  2. 2008.05.29 [펌] Makefile을 만들어보자
  3. 2008.01.18 embedded에서 메모리 관리(퍼온글)
  4. 2008.01.11 Windows 에서 GTK+설치하고 갖고 놀기(퍼온글) 3
  5. 2007.11.06 Volatile이란?(퍼온글)
  6. 2007.10.11 바이너리 rpm만들기(armv4l용)
  7. 2007.10.10 armv4l.rpm을 풀어라..
  8. 2007.09.19 cpu 사용율 구하기(3) - top명령 이용
  9. 2007.09.19 CPU 사용율 구하기(1)
  10. 2007.09.19 cpu load에 관한 자료

[펌]중요 string.h 함수

char* strcpy(char* p, const char *q); // q를 p로 복사한다(종료문자까지포함)

char* strcat(char* p, const char *q); // q를 p에 추가한다(종료문자까지 포함)

char* strncpy(char* p, const char *q, int n); // q에서 p로 n문자를 복사한다.

char* strncat(char* p, const char *q, int n); //q에서 p에 n문자를 추가한다.



size_t strlen(const char* p); //  p의 길이(종료문자는 세지 않는다)

int strcmp(const char* p, cosnt char* q); // 비교: p및 q

int strncmp(const char* p, const char* q, int n) //처음 n문자를 비교한다.


char* strchr(char *p, int c); // p에서 처음 c를 찾는다.

const char* strchr(const char* p, int c);

char* strrchr(char* p, int c); // p 에서 마지막 c를 찾는다.

const char* strrchr(const char* p, int c);

char* strstr(char* p, const char* q); // p에서 처음 q를 찾는다

const char* strstr(const char* p, const char* q);


char* strpbrk(char* p, const char* q); // q안에 있는 아무 문자와 일치하는 p 내의 첫 위치를 찾는다.

const char* strpbrk(const char* p, const char* q);


size_t strspn(const char* p, const char* q); // q에 없는 문자가 처음으로 p에 나타날 때까지의 문자개수

size_t strcspn(const char*p, const char * q); // q에 있는 문자가 처음으로 p에 나타날 때까지의 문자개수

[펌] Makefile을 만들어보자

 컴파일

 

작성한 코드에 구문검사를 해서 구문에 이상이 있다면 오류를 내주어 작성한 유저에게 알려주고 만약 검사에 이상이 없다면 프로그램이 시작되기 전에 메모리에 올라간다. 이처럼 컴파일의 역할은 유저가 작성한 코드에 이상이 없는지 검사하고 코드에 작성된 변수나 함수들을 메모리에 올려주는 역할을 한다.

 

 링크

 

프로그램을 만드는 마지막 작업이 바로 링크(Link)라는 과정이다. 필요한 조각들을 모두 모으거나 어떤 부분이 빠져 있는지 알아보기 위한 과정이다. 정적 라이브러리(Static Library)를 사용할 때, 링커는 프로그램이 필요로 하는 부분을 라이브러리에서 찾아서 그냥 실행파일에다 카피해버린다. 공유 라이브러리(또는 동적 라이브러리)의 경우에는 이렇게 하는 것이 아니라 실행파일에다가 단지 "실행될 때 우선 이 라이브러리를 로딩시킬 것"이라는 메시지만을 남겨놓는다. 당연히 공유 라이브러리를 사용하면 실행파일의 크기가 작아진다. 그들은 메모리도 또한 적게 차지하며, 하드 디스크의 용량도 적게 차지한다.

링킹(linking)은 여러가지 코드와 데이터를 묶어 메모리로 로드될 수 있는 하나의 실행 가능한 파일을 만드는 작업이다.

링킹은 컴파일-타임때 행해질 수도 있고, 로드-타임(로더에 의해), 혹은 런-타임(응용 프로그램에 의해)때도 행해질 수 있다.

 

Cygwin

 

윈도우 환경에서 GNU 프로그램을 사용할 수 있는 환경을 제공하는 프로그램

http://falinux.com/win/study/cygwin/cygwin1.html

Make Utility

1. 들어가기

 

프로그래머들은 작은 프로그램을 작성할 때는 단순히 수정하고 나서 모든 파일을 다시 컴파일 하여 새로 프로그램을 만들게 된다. 그러나 좀더 규모가 큰 프로그램에서는 이런 간단한 방법이 약간의 문제를 가져오게 될 것이다. 하나의 파일만 변경하게 되더라도 다시 컴파일 해야 하는 등의 수정하고 컴파일하고 테스트하는데 많은 시간을 소모하게 될 것이다. 이것 같은 문제를 해결하기 위해서 만들어 진 것이 make 유틸리티이다. 다시 정리하자면 이 유틸리티는 큰 프로그램을 유지하는 필요한 유틸리티이며, 프로그램 중 어느 부분이 새롭게 컴파일 되어야 하는지를 자동적으로 판단해서 커맨드(컴파일러, 쉘등)를 이용하여 프로그램을 재 컴파일을 하게 된다.

 

2. 왜 사용하지?

 

프로그램을 개발하게 되면 보통 라인 수가 증가하게 되어 하나의 파일만으로 프로그램을 구성하는 것은 어려운 일이다. 그래서 보통은 각각의 기능을 작은 모듈로 나누어 여러 파일로 나누어 개발을 하게 된다. 이 파일들은 서로 관계를 가지고 있는데, 어느 하나를 필요에 의해 바꾸게 되었을 때 그 파일에 있는 모듈(함수)를 이용하는 다른 파일도 새롭게 컴파일 되어야 한다. 하지만 파일 수가 많은 경우 이를 일일이 컴파일을 하게 될 때, 그 불편함과 함께 컴파일 하지 않아도 될 것도 컴파일을 하게 될 수 있고, 컴파일 해야 할 것도 미쳐 못하게 되어 링크시 에러를 발생하게 되는 경우도 있게 된다. 이런 상황에서 자동적으로 관계 있는 것만 새롭게 컴파일 하거나, 여러 개의 입력 파일로부터 출력 파일을 생성할 때 make 유틸리티는 필요하게 된다.

3. 어떻게 사용할까?

 

make 유틸리티는 많은 기능을 제공하지만, 자동으로 입력 파일을 인식하고, 컴파일 하여 출력 파일을 생성하지는 못하기 때문에 이 것에 대한 정보를 make가 알 수 있도록 파일을 제공해야 하는데 이것이 Makefile이라는 파일이다. 이것은 make가 이해할 수 있도록 일정한 형식을 가지고 있는 스크립트 언어와 흡사하다. Makefile에서는 시스템의 쉘을 사용할 수 있으며 컴파일러등의 프로그램들을 실행할 수 있다. Makefile은 대개 프로젝트의 다른 소스 파일과 같은 디렉토리에 존재하며, 큰 프로그램에서는 각 각의 기능들을 위해 독립적인 Makefile을 사용하여 프로그램을 관리한다.

4. Makefile에 대하여… …

 

메이크파일은 기본적으로 대상(Target), 의존 관계(dependency), 명령(command)등에 대한 규칙들(rules) 이루어져 있다. 아래의 list 4-1 기본적인 메이크파일의 플랫폼이다.

 

 

Target . . .  :  Dependency . . .

 

               Command  . . .

 

.                         . . .

. . .

List 4-1. Makefile 플랫폼

Note1

 

 

 


· Target : Command에 의해서 수행 되어서 나온 결과 파일을 지정하므로, 일반적으로 목적 파일(Object file)이나 실행 파일이다.

 

· Dependency : 생성되는 파일인 대상(Target)과 이것이 의존하는 파일들의 관계를 나타낸다.

 

· Command : Command 부분에 정의된 명령들은 의존 관계(Dependency)부분에 정의된 파일의 내용이 바뀌었거나, 대상(Target) 부분에 해당하는 파일이 없을 때 이곳에 정의된 것들이 차례대로 실행된다. 일반적으로 쉘에서 쓸 수 있는 모든 명령들을 사용할 수 있으며 Bash에 기반한 쉘 스크립트도 지원한다.

 

 

4.1. Makefile 예제 보기

 

· 간단한 Makefile 만들기

 

간단하게 Makefile 만들어 보자. 우리가 만들려고 하는 프로그램은 main.c, read.c, write.c 구성되어 있고 모두 io.h을 사용하고 read.c defs.h을 더 포함하고, wirte.c buffer.h라는 라는 헤더 파일을 사용한다고 가정한다. 이들을 각각 컴파일해서 test 라는 실행 파일을 생성시킨다 아래의 list 4-2 상에서 실행 파일을 만든 것이고, List 4-3 Makefile 작성하여 만든 것이다.

 

 

 $gcc -c main.c

 $gcc -c read.c

 $gcc -c write.c

 $gcc -o test main.o read.o write.o

List 4-2. 상에서 실행 파일 만들기

 

test: main.o read.o write.o

       gcc -o test main.o read.o write.o

 

  main.o : io.h main.c

       gcc -c main.c

 

  read.o : io.h defs.h read.c

       gcc -c read.c

 

  write.o : io.h buffer.h write.c

       gcc -c write.c

 

 

  $make

  gcc -c main.c

gcc -c main.c

gcc -c main.c

gcc -o test main.o read.o write.o

 

List 4-3. Makefile 작성하여 실행 파일 만들기와 실행 화면

Note2

 

 


· 쉘 상에서 make명령을 내리면 먼저 make는 먼저 현재 디렉토리에서 makefile이라는 파일을 찾는다. 이 파일이 존재하지 않으면 다시 Makefile이라는 파일을 찾는다.전통적인 유닉스 프로그래머는 makefile을 사용한다.

 

· make Makefile의 내용을 보고, 내부적으로 어떻게 파일들이 의존하고 있는지 조사한다.

 

· 위의 예제에서는 test라는 실행 파일을 만들기 위해서는 main.o io.h, read.o io.h defs.h, 그리고 write.o io.h buffer.h의 헤더 파일과 모두 자신의 소스에 의존 관계임을 알 수 있다. 만약에 main.c를 고친다면 main.o 컴파일되어 다시 생기고, test 다시 링크되어 갱신된다. 만약 io.h 바뀌었다고 가정하면 모든 파일들이 컴파일되어서 목적 파일이 생기고, 그것들이 링크가 되어 test 생긴다

 

· 변수를 사용하여 더 간단하게 만들기

 

위의 예제에서 변수를 사용하여 더 간단하게 Makefile을 작성한 것이 List 4-4 이다.

 

# Variables make Makefiles simpler

Objects = main.o read.o write.o

 

test: $(Objects)

       gcc -o test $(Objects)

 

  main.o : io.h main.c

       gcc -c main.c

 

  read.o : io.h defs.h read.c

       gcc -c read.c

 

  write.o : io.h buffer.h write.c

       gcc -c write.c

 

List 4-4. 변수을 이용하여 Makefile만들기

Note3

 

 


· Makefile에서의 주석문은 #으로 시작하고, 한 줄의 끝까지 계속된다.

 

· Makefile에서는 변수를 사용하기 위해서는 값을 대입하기 위해서는 “=”을 사용하고 $(변수), ${변수}의 형식을 사용하여 값을 쓸 수 있게 된다.

 

· Makefile에서 사용되는 모든 규칙은 탭으로 시작하는 줄에 있어야 한다. 빈 칸은 허용되지 않는다. 다수의 빈 칸과 탭은 같은 것으로 보이고, 유닉스 프로그래밍의 다른 모든 경우에 빈 칸과 탭 사이에는 거의 차이가 없으므로 이것은 문제를 일으킬 수 있다. 또한, Makefile에서 줄의 마지막에 있는 빈 칸은 make명령이 실패하게 할 수 있다.

 

· 명령들을 추론하여 만들기

 

make명령은 C 소스인 경우 .o을 이용하여 .c를 추론하여 명령을 내부 규칙에 따라 gcc -c을 수행하게 된다. 아래의 List 4-5는 이것을 보여 주고 있다.

 

 

#Letting make deduce the commands

OBJECTS = main.o read.o write.o

 

test: $( OBJECTS)

       gcc -o test $( OBJECTS)

 

  main.o : io.h

 

  read.o : io.h defs.h

 

write.o : io.h buffer.h

 

.PHONY : clean

clean:

      -rm –f $( OBJECTS)

      @rm –f test

     

List 4-5. 명령을 추론하여 만든 Makefile

Note4

 

 


· 위의 clean처럼 대상(Target) 부분에 해당하는 부분이 그냥 레이블과 같이 사용될 수 있다. 이것을 Phony Target이라고 하면 위의 형식처럼 사용하는데 .PHONY는 생략하여도 된다.

 

· 위의 예제에서 rm명령 앞에 있는“-“ make가 에러를 무시하게 한다. 예를 들어, 디렉토리를 만들기 원하고 디렉토리가 이미 존재할 경우에 에러를 무시하기 원한다면, mkdir 앞에 “-“를 추가하면 된다.

 

· 위의 예제에서 rm명령 앞에 있는 @ make가 명령을 실행하는 것을 표준 출력 장치로 출력하지 않도록 지시한다.

 

 

· 또 다른 형태의 Makefile 만들기

 

내부 규칙에 따라 Target대신에 의존성(dependency)만을 사용하여 만들 것이 List 4-6이다.

 

# Alternative sytle of makefile

 

OBJECTS = main.o read.o write.o

 

test: $( OBJECTS)

       gcc -o test $( OBJECTS)

 

  $(Objects) : io.h

 

  read.o : defs.h

 

  write.o : buffer.h

 

List 4-6. 의존성을 이용하여 Makefile만들기

 

4.2. Makefile의 기능을 향상시키 주는 매크로 와 확장자 규칙

 

· 매크로(Macro)란 무엇일까?

 

매크로는 특정한 코드를 간단하게 표현하기 위해 사용되며,프로그램을 작성할 때 변수를 지정하는 것처럼 하면된다. 매크로를 Makefile에서 사용하기 위해서는 $(..) ${..}, $를 사용하는데, 일반적으로 $(..)을 사용하기를 권고한다. 아래의 List 4-7은 매크로의 사용 방법을 보여 주고 있다.

 

 

# Macro makes Makefile happy.

 

OBJECTS = main.o read.o write.o

 

test: $( OBJECTS)

       gcc -o test $( OBJECTS)

 

  $(OBJECTS) : io.h

 

  read.o : defs.h

 

  write.o : buffer.h

 

List 4-7. 간단한 매크로 예제

 

· 미리 정해져 있는 매크로(Macro)들의 무엇이 있을까?

 

프로그래머가 매크로를 만들어 사용할 수 있지만, 미리 정의된 매크로들이 있다. 이 매크로들을 보기 위해서는 make p라고 입력해 보면 make에서 미리 정의 되어 있는 모든 값들(매크로, 환경 변수등등)을 볼 수 있다. 아래의 List 4-8은 미리 정의된 매크로를 보여 주고 있으며, List 4-9는 미리 정의 된 매크로를 사용하여 만든 예제이다.

 

 

ASFLAGS = (as 명령어의 옵션 세팅)
AS = gas
CFLAGS = (gcc 의 옵션 세팅)
CC = gcc 
CPPFLAGS = (g++ 의 옵션)
CPP = g++
LDFLAGS = (ld 의 옵션 세팅)
LD = ld

List 4-8. 미리 정의된 매크로

Note5

 

 


· 매크로는 관습적으로 대문자를 작성된다.

 

· 쉘 상에서 정의한 환경 변수 값들은 그대로 이용된다.

 

 

 
OBJECTS = main.o read.o write.o
SRCS = main.c read.c write.c
 
CC = gcc
CFLAGS = -g -c
 
TARGET = test 
 
$(TARGET) : $(OBJECTS)
$(CC) -o $(TARGET) $(OBJECTS)
 
clean : 
rm -f $(OBJECTS) $(TARGET)
 
main.o : io.h main.c
read.o : io.h read.c
write.o: io.h write.c
 
$make
gcc -g -c main.c -o main.o
gcc -g -c read.c -o read.o
gcc -g -c write.c -o write.o
gcc -o test main.o read.o write.o
 
$make clean
rm -rf main.o read.o write.o test
 

List 4-9. 미리 정의된 매크로를 사용한 예제 와 실행 화면

Note6

 

 


· CFLAGS 같은 매크로는 make 파일의 내부에서 이용하여 .c파일을 .o파일로 바꾸어 주는 부분이 없어도 CFLAGS에 값을 세팅하면 make가 알아서 컴파일을 수행하여 .o파일을 생성한다.

 

 

· 확장자 규칙(Suffix rule)란 무엇인가?

 

파일의 확장자를 보고, 그에 따라 적절한 연산을 수행시키는 규칙이다. 가령 가령 .c 파일은 일반적으로 C 소스 코드를 가리키며, .o 파일은 목적 파일(Object file) 말하고 있다. 그리고 당연히 .c 파일은 컴파일되어서 .o 파일이 되어야 하는 것이다. .SUFFIXS라는 매크로를 사용하여 확장자를 등록하여 있다. 아래의 List 4-10 .SUFFIXES 매크로를 이용한 예제이다.

 

 
 .SUFFIXES : .c .o
 
 OBJECTS = main.o read.o write.o
 SRCS = main.c read.c write.c
 
 CC = gcc
 CFLAGS = -g –c
 
 TARGET = test
 
 $(TARGET) : $(OBJECTS)
      $(CC) –o $(TARGET) $(OBJECTS)
 
 clean:
      rm –f $(OBJECTS) $(TARGET)
 
 main.o : io.h main.c
 read.o : io.h defs.h read.c
 write.o : io.h buffer.h write.c
 
 
 $make
 gcc -g -c main.c -o main.o
 gcc -g -c read.c -o read.o
 gcc -g -c write.c -o write.o
 gcc -o test main.o read.o write.o
 

List 4-10. .SUFFIXES를 사용한 예제와 실행 화면

 

 

Note7

 

 


· 확장자 규칙에 의해서 make는 파일들 간의 확장자를 자동으로 인식해서 필요한 작업을 자동으로 수행한다. 위의 예제에서는 자동적으로 아래와 같은 루틴이 자동적으로 동작하게 된다.

 

.c .o :

$(CC) $(CFLAGS) -c $< -o $@

 

· make 내부에서 기본적으로 서비스를 제공해 주는 확장자는 다음과 같다.

.out .a .ln .o .c .cc .C .p .f .F .r .y .l .s .S .mod .sym .def .h .info .dvi .tex .texinfo .texi .txinfo

 

· Makefile내부에서 .SUFFIXES 매크로의 값을 세팅해 주면 내부적으로 정의된 확장자의 연산이 동작을 하게 된다. 따라서 확장자 규칙은 make가 어느 확장자를 가진 파일들을 처리할 것인가를 정해 주는 것이다.

 

 

· 내부 매크로(Internal macro)무엇일까?

 

make에서는 내부 매크로라는 것이 있다. 이것은 우리가 맘대로 정할 수 있는 매크로는 절대 아니다. 대신 매크로를 연산, 처리하는데 쓰이는 매크로라고 하는 것이 더 적당할 것이다. 아래의 Table 4-1은 내부 매크로에 대해 설명하고 있으며, List 4-11은 내부 매크로를 사용한 예를 보여 주고 있다.

 

내부 매크로

         

$*

확장자가 없는 현재의 대상(Target) 파일

$@

현재의 대상(Target) 파일

$<

현재의 대상(Target) 파일보다 더 최근에 갱신된 파일 이름

(dependency중에서 처음에 위치한 파일 이름)

$?

현재의 대상(Target) 파일보다 더 최근에 갱신된 파일 이름

(모든 dependency 파일의 이름)

Table 4-1. 내부 매크로

 

 
 main.o : main.c io.h
     gcc -c $*.c ($* 는 확장자가 없는 현재의 대상 파일이므로 $* 는 결국 main에 해당된다.)
 
 test : $(OBJECTS)
     gcc –o $@ $*.c ($@ 는 현재의 대상 파일이므로 $* 는 결국 test에 해당된다.)
 
 .c .o :
     gcc -c $< (또는 gcc -c $*.c)
($< 는 현재의 대상 파일보다 더 최근에 갱싱된 파일 이름이므로, .o 파일보다 더 최근에 갱신된 .c 파일은 자동적으로 컴파일이 된다. 가령 main.o를 만들고 난 다음에 main.c를 갱신하게 되면 main.c $<의 작용에 의해 새롭게 컴파일 된다.)

List 4-11은 내부 매크로를 사용한 예제

4.3 Makefile를 작성할 때 알면 좋은 것들

 

· 긴 명령어를 여러 라인으로 표시하기

 

Makefile을 작성할 때 명령어가 한 줄을 넘어간다면, \문자를 이용해서 여러 라인으로 나타낼 수 있다. 아래의 List 4-12은 긴 명려어를 여러 라인으로 나타내는 예제이다.

 

 

OBJECTS = shape.o \

          rectangle.o \

          circle.o \

          line.o \

          main.o \

          read.o \

          write.o \

List 4-12. 여러 라인으로 명령어 나타내는 예제

 

· 매크로 치환(Macro substitution)

 

매크로를 지정하고, 그것을 이용하는 것을 이미 알고 있다. 그런데 필용에 의해 매크로의 내용을 조그만 바꾸어야 할 때가 있다. 매크로 내용의 일부만 바꾸기 위해서는 $(MACRO_NAME:OLD=NEW)과 같은 형식을 이용하면 된다. 아래의 List 4-13은 매크로 치환에 대한 예제이다.

 

 

MY_NAME = Hello World

YOUR_NAME = $(MY_NAME:Hello=Hi)

 

(Jack이란 부분이 Jook으로 바뀌게 된다. YOUR_NAME이란 매크로의 값은 Hi World이 된다.)

 

 

OBJS = main.o read.o write.o

SRCS = $(OBJS:.o=.c)

 

(SRCS에서는 OBJS에서 .o .c로 바뀌게 된다. 다음과 같이 변경되는 것이다.

SRCS = main.c read.c write.c)

List 4-13. 매크로 치환에 대한 예제

 

· 자동 의존 관계 생성(Automatic dependency)

 

일반적인 make 구조는 대상(target), 의존 관계(dependency), 명령(command)이 연속적으로 정의 되어 있는데 실행되는데, 명령이 없이 대상과 의존 관계만 표시가 되면 이는 대상이 어는 파일에 의존하고 있는지 표시해 주는 정보 역할을 하게 됩니다. Makefile을 작성할 때 없어서는 안되는 부분입니다. 그런데 일일이 이런 정보를 만든다는 것은 쉬운 일이 아닙니다. 이런 단조롭고 귀찮은 일을 자동으로 해주기 위해서 gcc M XX.c의 형식을 사용하여 의존 관계를 만들게 된다. 프로그램을 설치할 때 make dep라는 것을 친 기억이 있을 것이다. 파일들의 의존 관계를 작성해 준다는 의미이다. List 4-14는 자동 의존 관계를 생성하는 예제이다.

 

 

.SUFFIXES : .c .o 
CFLAGS = -O2 -g
 
OBJS = main.o read.o write.o 
SRCS = $(OBJS:.o=.c)
 
test : $(OBJS)
          $(CC) -o test $(OBJS)
 
dep :
$(CC) –M $(SRCS)
 
 $make dep
 $vi Makefile
 
main.o: main.c /usr/include/stdio.h /usr/include/features.h \
/usr/include/sys/cdefs.h /usr/include/libio.h \
/usr/include/_G_config.h io.h
read.o: read.c io.h defs.h
write.o: write.c io.h buffer.h
  (Makefile의 뒷부분에 위에 내용이 첨가 된다.)

 

List 4-14. 자동 의존 관계 생성 예제 및 실행화면

 

 

 

 

 

 

 

 

 

 

 

 

 

· 다중 타켓(Multiple Target)

 

하나의 Makefile에서 꼭 하나의 결과만 만들어 내라는 법은 없다. 결과 파일이 3개가 필요한 경우도 있다. 아래의 List 4-15는 다중 대상(Target)에 대한 예제이다.

 

 

.SUFFIXES : .c .o 
CC = gcc
CFLAGS = -O2 -g
 
OBJS1 = main.o test1.o
OBJS2 = main.o test2.o 
OBJS3 = main.o test3.o 
SRCS = $(OBJS1:.o=.c) $(OBJS2:.o=.c) $(OBJS3:.o=.c) 
 
all : test1 test2 test3
 
test1 : $(OBJS1)
        $(CC) -o test1 $(OBJS1) 
 
test2 : $(OBJS2)
        $(CC) -o test2 $(OBJS2)
 
test3 : $(OBJS3)
        $(CC) -o test3 $(OBJS3)
 
deep :
        $(CC) -M $(SRCS)
 
$make all (또는 make)
gcc -O2 -g -c main.c -o main.o
gcc -O2 -g -c test1.c -o test1.o
gcc -o test1 main.o test1.o ( test1 의 생성 ) 
gcc -O2 -g -c test2.c -o test2.o
gcc -o test2 main.o test2.o ( test2 의 생성 )
gcc -O2 -g -c test3.c -o test3.o
gcc -o test3 main.o test3.o ( test3 의 생성 )
 

 

List 4-15. 다중 대상(Target)에 대한 예제 및 실행 화면.

 

 

 

 

· 순환 make(Recursive make)

 

규모가 큰 프로그램들은 파일들이 하나의 디렉토리에 있지 않는 경우가 많다. 여러 개의 서브시스템이 전체 시스템을 구성한다고 가정하면 각 서브시스템에 Makefile이 존재한다. (서브시스템 = 서브디렉토리) 따라서 여러 개의 Makefile을 동작시킬 필요가 있도록 Makefile을 고쳐 보자. 서브디렉토리에 있는 Makefile을 동작시키는 방법은 의외로 간단하다. 아래의 List 4-16은 순환 make에 대한 예를 보여 주고 있다.

 

subsystem:
                cd subdir; $(MAKE) .....(1)
 
subsystem:
                $(MAKE) -C subdir .....(2)

 

(위의 예제에서 (1) (2)는 동일한 명령을 수행한다 우리가 만들 시스템의 타겟이 subsystem이다. 우선 subdir이라는 곳으로 가서, 거기에 있는 Makefile을 동작시키게 된다. MAKE라는 것은 그냥 make라는 명령어를 표시하는 매크로일 뿐이다.)

 

SUFFIXES : .c .o
CC = gcc
CFLAGS = -O2 -g
 
all : DataBase Test <- 요기에 집중.
 
DataBase:
    cd db ; $(MAKE)  (db 로 이동해서 make 실행 )
 
Test: 
    cd test ; $(Make)  ( db 로 이동해서 make 실행 )

 

$make
cd db ; make
make[1]: Entering directory`/home/raxis/TEST/src'
gcc -O2 -g -c DBopen.c -o DBopen.o
gcc -O2 -g -c DBread.c -o DBread.o
gcc -O2 -g -c DBwrite.c -o DBwrite.o
make[1]: Leaving directory `/home/windows/TEST/src'
cd test ; make
make[1]: Entering directory `/home/raxis/TEST/test'
gcc -O2 -g -c test.c -o test.o
make[1]: Leaving directory `/home/windows/TEST/test'

(make뒤의 대괄호의 1이라고 나타난 것은 현재의 레벨을 의미한다. 원래 디렉토리의 레벨이 0이고, 여기서는 레벨이 하나 더 내려갔으므로 1이라고 표시된 것이다.)

List 4-16. 순환 make 대한 예제 및 실행 화면

· 불필요한 재컴파일 막기

 

의존 관계 규칙에 의해 하나가 바뀌면 그에 영향받는 모든 파일이 바뀐다고 앞에서 말했다. 그러나 다른 파일들에게 아무 영향을 주지 않도록 수정하였는데도 재컴파일을 시도한다면 시간 낭비가 될 수도 있다. 가령 모든 .c 파일에서 include 하는 헤더 파일에서 새로운 #define PI 3.14 라고 정의를 했다고 가정하자. 그리고 PI라는 것은 아무 곳에서도 사용을 하지 않는다. 이때는 'make -t' 라고 해보자. -t touch를 의미하는 옵션으로써 컴파일을 하지 않는 대신 파일의 생성 날짜만 가장 최근으로 바꾸어 놓는다. 새로 컴파일 된 것처럼 처리를 하는 것이다. touch유틸리티 명렁어에 익숙한 사람이라면 이해할 것이다. touch는 파일의 생성 날짜를 현재로 바꾸어 주는 간단한 유틸리티이다.

4.4 Makefile에 사용되는 옵션

make에서 거의 모든 것은 Makefile내부에서 모두 지정을 할 수 있다. 그중 일부를 make의 실행 시에 옵션으로 통해서 줄 수도 있다. List 4-17은 수많은 옵션중에서 가장 많이 사용 옵션을 보여 주고 있다.

-C dir

위에서도 밝혔듯이 Makefile을 계속 읽지 말고 우선은 dir로 이동하라는 것이다. 순환 make에 사용된다.

 

-d

Makefile을 수행하면서 각종 정보를 모조리 출력해 준다. (-debug) 결과를 파일로 저장해서 읽어보면 make 의 동작을 대충 이해할 수 있다.

 

-h  옵션에 관한 도움말을 출력한다. (-help)

 

-f file  file 에 해당하는 파일을 Makefile로써 취급한다. (-file)

 

-r

내장하고 있는 각종 규칙(Suffix rule )을 없는 것으로 (-no-builtin-rules)간주한다. 따라서 사용자가 규칙을 새롭게 정의해 주어야 한다.

 

-t  파일의 생성 날짜를 현재 시간으로 갱신한다. (-touch)

 

-v  make의 버전을 출력한다. ( GNU make 3.73 을 씁니다.) (-version)

 

-p  make에서 내부적으로 세팅되어 있는 값들을 출력한다. (-print-data-base)

 

-k  

make는 에러가 발생하면 도중에 실행을 포기하게 되는데 (-keep-going) -k 는 에러가 나더라도 멈추지 말고 계속 진행하라는 뜻.

List 4-17. Makefile의 옵션들

4.5 Makefile의 실제 예제

아래의 List 4-18 List 4-19 Makefile 예를 보여 주고 있다.

 

HOSTARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \

           -e s/arm.*/arm/ -e s/sa110/arm/ -e s/macppc/ppc/)

 

HOSTOS := $(shell uname -s | tr A-Z a-z)

 

ifeq ($(HOSTARCH),ppc)

CROSS_COMPILE =

else

CROSS_COMPILE =

endif

 

export CROSS_COMPILE HOSTARCH

 

TOPDIR := $(shell if [ "$$PWD" != "" ]; then echo $$PWD; else pwd; fi)

 

export TOPDIR

 

include $(TOPDIR)/config.mk

          

SUBDIRS = common driver

 

#OBJS = ascu/libascu.a

OBJS += driver/libdriver.a

#OBJS += net/libnet.a

OBJS +=        common/libcommon.a

 

all:   ascu

       @for dir in $(SUBDIRS); \

       do \

               $(MAKE) -C $$dir || exit 1 ; \

       done

 

ascu:  depend subdirs $(OBJS) $(LDSCRIPT)

               $(CC) -o ascu_prog $(OBJS)  -D_REENTRANT -lpthread

 

subdirs:

               @for dir in $(SUBDIRS) ; \

               do \

                        $(MAKE) -C $$dir || exit 1 ; \

               done

 

depend dep:

               @for dir in $(SUBDIRS) ; \

               do \

                        $(MAKE) -C $$dir .depend ; \

               done    

 

clean:

       rm -f `find . -type f \

               \( -name '*.o' -o -name '*.a' \) -print`

       rm       -f ascu_prog ascu.elf ascu.map

 

clobber:       clean

       rm -f `find . -type f \

               \( -name .depend -name '*.o' -o -name '*.a' \) \

               -print`

       rm -f ascu_prog ascu.elf ascu.map

List 4-18  첫번째 Makefile 예제

 

include $(TOPDIR)/config.mk

 

LIB = ascu

 

LIBDIR = lib$(LIB).a

 

OBJS   = $(patsubst %.c,%.o,$(wildcard *.c))

 

$(LIBDIR):     .depend $(OBJS)

       $(AR) crv $@ $^

 

#########################################################################

 

.depend:       Makefile $(SOBJS:.o=.S) $(OBJS:.o=.c)

               $(CC) -M $(CFLAGS) $(SOBJS:.o=.S) $(OBJS:.o=.c) > $@

 

sinclude .depend

 

#########################################################################

 

List 4-19  두번째 Makefile 예제

아래의 List 4-20 List 4-19 Makefile에 의해 생성된 dependency 파일을 보여 주고 있다.

 

 

main.o: main.c /usr/include/stdio.h /usr/include/features.h \

 /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \

 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stddef.h \

 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stdarg.h \

 /usr/include/bits/types.h /usr/include/libio.h \

 /usr/include/_G_config.h /usr/include/bits/stdio_lim.h \

 /usr/include/bits/stdio.h

List 4-20  생성된 dependency 파일(.depend)

 

embedded에서 메모리 관리(퍼온글)

[응용] 메모리 관리
글쓴이 : 유영창 (2004년 09월 17일 오후 05:54) 읽은수: 6,520 [ 임베디드강좌/유영창 인쇄용 페이지 ]
APK006 메모리 관리
==============================================

1. 개요

이 문서는 ESP-NS에서 동작하는 응용 프로그램을
작성할때 메모리 할당과 해제및 메모리 처리에
대한 주의점을 소개합니다.

작성자 : 유영창 frog@falinux.com
작성일 : 2004년 9월 17일
수정일 :

관련된 ADK( Application Developer Kit ) 디렉토리

adk/sample/check_index
adk/sample/assert


2. 임베디드에서 메모리 관련 문제

임베디드 시스템은 사용시간에 따라서 크게 두가지로
나누어 볼수 있읍니다.

첫번째는 필요한 경우만 전원을 넣고 동작시켜 사용하는 경우로
동작 시간이 짧은 경우입니다. (참으로 고마운 시스템입니다.)

두번째는 모니터링 시스템같이 지속적인 제어가 필요하여
1년 365일 전원이 절대로 나가면 안되는 경우 입니다.

프로그래머 입장에서 보면 첫번째 방식을 좋아 하게 됩니다.
이건 무정전 시스템에 사용되는 프로그램을 작성하신분들이라면
온몸으로 느끼는 감정입니다. ( 해본 사람들은 압니다. ㅜㅜ )


시스템이 무정전으로 동작한다는 것은 여러가지를 고려 해야 합니다.
그중 으뜸은 메모리 누수 입니다.

C 로 작성하는 프로그램은 반드시 메모리에 관련된 문제 때문에
한번 이상은 반드시 고생하게 됩니다.

더구나 C 언어에 익숙하지 않으신 분이라면 포인터 참조에 관련된
수 많은 버그로 엄..청..난... 고생을 합니다.

그래도

납품하기전에 메모리 관련된 버그 문제점을 알게 되면 그나마
다행입니다.

그러나 프로그래머 입장에서 두고 두고 속썩이는 것중 하나가
장기간 동작하다 멈추는 경우입니다.

프로그램을 수정해서 버그를 잡았는지 확인하려고 하면
몇일씩이나 걸리기 때문에 프로그래머들을 미치기 일보
직전까지 만듭니다. ( 대부분의 경우 어디에서 발생했는지도
잘 모르죠.. )

이런 경험을 여러번 하다보면 나름대로의 방법론이 생깁니다.

이런 경험과 관련되어 메모리를 다루는 방법에 대한 몇(?)가지와
메모리 할당과 해제에 관련된 함수를 소개하려고 합니다.


3. 배열을 사용하라....

PC 프로그램을 작성하시던 분들이 임베디드 시스템에서
프로그램을 작성할때 가장 걱정되는 습관 중 하나가
메모리 할당과 해제를 아주 좋아 한다는 겁니다.

PC 시스템에서 작성하는 프로그램은 실장된 메모리가
많기 때문에 메모리 할당과 해제를 이용하면
유연한 프로그램이 가능해 집니다.

그..러..나..

임베디드에는 메모리 할당과 해제를 자주 이용하는 습관은
절대적으로 말리고 싶은 것 습관중 하나입니다.

보통 제품을 설계하는 분들이 개발자들에게 요구하는 것중
하나가 만능제품이죠...

그런데 이 만능은 프로그래머가 만능이 되어야 합니다.
이런 경우에도 적용될수 있고 저런 경우에도 적용될수 있고

마음약한 개발자들은 이런 요구를 수용합니다.

그러다 보니 개발해야 하는 프로그램 구조가 요구 사항에
가변적인 구조를 가지게 되죠..

결국 메모리 할당 구조와 리스트와 같은 자료 구조를 사용하게
됩니다.

이때부터 개발자는 머리털 빠지기 시작합니다.
( 제가 속빈 인간이 된 사연이 여기에 있읍니다. )
리스트구조와 메모리 할당은 시스템의 버그 원인 순위에
가장 상위 순위를 차지 합니다.

오랜 연륜을 가지는 개발자들은 일단 이런 영업 요구에
적절히 대항합니다.

그리고 어느정도 제품이 필요한 요구 사항을 제한합니다.
그리고 그에 맞게 프로그램을 개발합니다.

이때 이 분들이 작성한 프로그램을 보면 ( 무정전 제품에
들어가는 프로그램일수록 ) 전역변수와 배열을 많이 사용하게
됩니다.

이 전역변수와 배열을 사용하는 것은
프로그램을 처음 배울때 회피하라고 들었던 것인데
의외로 고수일수록 많이 사용합니다.
(심지어 goto 문을 남발하시는 고수도 많습니다. )

배열을 사용한다는 것은 일정한 크기를 갖기 때문에
확장성에 용이하지 않을 것 같은데
환경 파일로 모든 확장성을 고려하는 것은 임베디드
제품에 크게 의미가 없읍니다.

이미 한정된 크기의 메모리를 가지고 있는 시스템에
확장할수 있는 크기를 가지도록 프로그램을 작성한다는 것은
의미가 없읍니다.


배열을 사용하게 되면 다음과 같은 장점이 있읍니다

1) 메모리 할당과 해제와 관련된 버그가 없다.
2) 메모리 할당과 해제에 소모되는 시간 소모가 없다.
3) 인덱스 방식의 참조가 가능하므로 잘못된 포인터
참조에 의한 버그 가능성이 작다.
4) 시스템의 메모리를 효율적으로 사용할수 있다.
5) 참조 속도가 매우 빠르다.
6) 프로그램 소스 코드가 직관적이다.
( 포인터 참조 연산자가 얼마나 어려운 코드 형식을
작는지 다들 아시죠? )

등등의 장점이외에 더 있지만 생각이 안 나는 군요...

어쩄든 임베디드 장비에 사용되는 프로그램은 가급적 배열을
사용하시는 것이 좋습니다.

3. 가급적 상수를 매크로로 정의해서 사용하라

메모리 이야기에 왠 매크로 상수?

뭐 이렇게 궁금하게 생각하시는 분들이 있을 것 같은데...
이래야 고수 소리를 듣습니다. 소스에 숫자가 적게 보일수록 고수 입니다


예를 들어 보죠...

어떤 분은 프로그램을 이렇게 작성합니다.

char check_ids[300];

고수는 이렇게 작성합니다.

#define MAX_CHECK_IDS 300
char check_ids[MAX_CHECK_IDS];


이것은 나중에 확장성을 가지는 효과가 있고
접근하는 인덱스의 검사를 할 경우에 유용합니다.


또한 인데스 검사를 하는 경우에 유용합니다.
보통 프로그램을 작성할때 인덱스 접근에 대하여
다음과 같이 처리하면 좋습니다.


char get_date( int index )
{
#ifndef NO_CHECK_INDEX_FLAG
if( (index < 0) || (index >= MAX_CHECK_IDS ) )
{
dlp( "index over\n" );
exit(0);
}
#endif
return check_ids[index];
}

또는 아예 인덱스 검사를 하는 함수를 매크로로 만들어서 사용하는 경우도 있읍니다.

선언예)

#ifndef NO_CHECK_INDEX_FLAG
#define check_index(idx,max) {\
if( (index < 0) || (index >= MAX_CHECK_IDS ) ) \
{ \
dlp( "index over\n" ); \
exit(0); \
} \
}
#else
#define check_index(idx,max) {}
#endif


사용예)

char get_date( int index )
{
check_index(index,MAX_CHECK_IDS);
return check_ids[index];
}

3. 초기화를 꼭 하라

변수를 사용할때 특히 전역 변수를 사용할때
초기화를 하는 습관은 버그를 예방하는 효과가 있읍니다.
특히 포인터 형식의 필드변수를 포함하는 구조체가 있을 경우에는 특히나 그렇습니다.

초기화 값은 0으로 사용하는 것이 좋습니다.
포인터의

4. sizeof 함수를 즐겨 사용하라

메모리 복사나 초기화를 사용할 경우와 같이 배열이나 구조체의 크기를 구할 필요가
있을때 sizeof 를 자주 사용합니다.

귀찮아서 그냥 숫자를 주는 습관을 가진 분들이 있는데
이런 분들에게 sizeof 함수의 사용을 강력하게 권장합니다.

앞의 배열에서 초기화를 처리할때 다음과 같은 형식으로 처리하는 것이 좋죠...

memset( check_ids, 0, sizoef( check_ids ) );

복사할 경우에 역시 이런 식으로 사용하는 것이 좋습니다.
하지만 복사할 경우에 크기는 어느 것을 사용하는 것이 좋을까요?
권장하는 것은 앞에것을 사용 하는 것입니다

void copy_item( struct a *bdata )
{
struct a adata;

// 권장하는 예
memcpy( &adata, bdata, sizeof( adata ) );

// 별로 권장하지 않지만 좋은 예
memcpy( &adata, bdata, sizeof( struct a ) );


}


5. 포인터의 간접 인덱스를 사용할 때는 주의하라...

포인터 변수를 사용할때 포인터의 초보자들이 실수하는 큰 것
중에 하나는 다음입니다.
특히 하드웨어를 다룰때 많이들 실수 합니다.

char *app;
int *bpp;

app++;
bpp++:

이것은 1씩 증가 시키는 겁니다.
그런데 app 나 bpp 에 0x300000 이라는 주소값이 있다면
app는 0x30000 이 되지만 bpp 는 0x300004 가 된다는 것을
까먹습니다.

이것이 나중에 속썩일 경우가 많다는 점을 기억하십시오

(app+ 1) 과 (bpp+ 1) 도 마찬가지 입니다.
이런것은 매크로를 사용해서 선언할 경우 많이들 실수하는 겁니다.

예를 들어 하드웨어 레지스터를 접근하는 경우에

#define REG_A(x) (x + 1)
#define REG_B(x) (x + 2)

이런식으로 처리할때 매크로는 단순히 문자열 치환이기 때문에
위와 같은 문제가 발생할수 있다는 것입니다.


6. 스택변수 즉 로컬 변수를 조심하자

함수안에 선언하는 로컬 변수는 두가지 장점이 있읍니다.

선언이 간단하고 할당에 걸리는 시간이 없다는 것입니다.
그러나 이 로컬 변수는 버그의 온상이 되므로 주의할 필요가 있습니다.

예를 들어 다음과 같은 경우를 생각해 봅시다.

int test_func( char *tmpstr )
{
char buff[32];
int p;

p = strlen( tmpstr) ;

sprintf( buff, "ID:%s", tmpstr );
write_func( buff );

return p;
}

이 함수를 소스상에서 본다면 아무런 문제가 발생하지 않는 함수입니다.
그런데 이 함수는 두가지 문제점을 가지고 있읍니다.

만약 tmpstr 이 NUL 코드를 포함하지 않는다면?
또는 tmpstr 이 28 자 이상이 된다면 ?

아...

프로그램은 어떻게 동작할지 아무도 장담할 수 없읍니다.

다행히 세그먼트 폴트라도 발생해서 미리 알수있다면 좋지만
스택을 접근 할 경우에는 세그먼트 폴트가 잘 발생하지 않습니다.

경우에 따라서는 스택이 깨지기 때문에 엄한 곳으로 프로그램이 점프할수도 있고
다른 변수들이 수정될수도 있읍니다.

더구나 특별한 경우에는 두번 호출되어 도착한 함수가 꺼꾸로 리턴될때
중간 함수를 거치지 않고 리턴되거나 진행 루틴이 실행되지 않을 경우도 있읍니다.

또는 뒤에 선언된 변수들 값이 수정될수도 있읍니다.

이런 경우에는 스택 변수의 크기를 아끼지 않는 것이 가장 최선의 예방책입니다.
(물론 주의해서 작성하는 것이 더 큰 최선의 에방책이죠.. )

예를 들어 넘어온 크기보다 2 배나 3 배정도의 크기를 잡는 겁니다.

위의 경우에는 char buff[128] 정도로 선언하는 것이 안전합니다.

또는 버퍼의 뒷쪽에 임의 변수를 하나 두는 것도 요령인데
별로 추천은 하고 싶지 않군요


7. 자료구조를 사용한다면 검증된 라이브러리를 사용하라..

프로그램을 작성하다보면 배열보다 리스트와 같은 자료구조를
이용하는 것이 효율적일때가 있읍니다.

대표적인 것들이

스택이나 , 큐, 더블 링크드 리스트 , 리스트, 이진 트리 리스트
등등이 있읍니다.

이때 많은 분들은 직접 만들어 사용합니다.

그런데 이런 처리는 포인터를 사용해야 하고 저같이 논리에 약한
사람들이 만들면 버그가 살기에 좋은 환경을 제공합니다.

그래서 저는 인터넷에 관련 자료 구조용 공개된 소스를 이용하기를
권장합니다.

특히 소스포지에 가면 이런 자료 구조체 라이브러리들이 많이 있읍니다.
가급적 이렇게 공개되고 여러사람이 사용하고 있는 것을 이용하기를
바랍니다.

직접 만들면 피 봅니다... ㅜㅜ

8. 메모리 할당 함수들

C 에서 메모리를 할당하기 위해서 사용하는 함수들은 다음과 같습니다.

void *calloc(size_t nmemb, size_t size); // 할당 + 메모리 클리어
void *malloc(size_t size); // 할당
void free(void *ptr); // 해제
void *realloc(void *ptr, size_t size); // 재 할당 + 메모리 복사

이 중에서 가장 많이 사용하는 함수는

malloc 함수와 free죠...

하지만 malloc 함수보다는 calloc 함수를 사용하기를 권장합니다.
(저역시 malloc 함수를 자주 사용합니다만 ㅜㅜ )
그래도 malloc 함수를 자주 사용하게 되면 다음과 같은 처리를 꼭 해주시기를 바랍니다.

char *buff = NULL;

buff = malloc( 1000 );
if( buff != NULL )
{
memset( buff, 0, 1000 );
}
else
{
// 에러 처리 ( 보통은 프로그램을 죽인다. )
}

if( buff != NULL ) free( buff );


9. assert 함수


앞에서 malloc 함수를 처리할때 에러가 난 경우에 대한 처리가 귀찮죠?
이런 경우 사용하거나 기타 등등에 사용하면 좋은 함수가 assert 함수입니다.


이 함수는 #include <assert.h> 를 포함하고 사용하면 되는데
사용 문법은 간단합니다.

assert( 논리식 );

이 함수는 논리식이 참이면 특별한 것을 하지 않습니다.
그러나 거짓이면 에러를 표현 합니다.
파일명과 함수명 그리고 라인번호와 함께 문제가 된 값을 표현합니다.
그리고 프로그램을 죽입니다.( 으으 살벌.. )

보통은 포인터 변수의 값이 0이 되는 것을 방지하기 위해서 사용합니다.

assert 함수는 NDEBUG 가 정의 되어 있으면 아무런 것도 하지 않는
함수이므로 소스를 수정하지 않고서도 함수의 에러 처리를 무효화
할 수 있는 무척 좋은 함수 입니다.


<<<  댓 글 >>>

답장 korone.net (2004년 09월 18일 오후 12:48)
글 잘 읽었습니다.
글에 첨언해서 개인적인 견해를 몇가지를 이야기 해보자 이렇게 글을 씁니다.

우선,

3. 배열을 사용하라에서 장점에 대해서 열거해 주셨는데요.
1) 메모리 할당과 해제와 관련된 버그가 없다.
--> 버그가 없다고 말할 수 없습니다.
포인터 사용에서 발생되는 잘못된 주소의 엑스스로 인한 segmentation fault같은거와
마찬가지로, 배열의 인덱스를 잘못 엑세스할 수 있는 버그가 동일하게
발생될 수 있습니다. 가령 이런경우죠.
char aaa[10];
aaa[11] = 'd';
이런식으로 사용한다면 이 결과는 포인터와 마찬가지의 결과를 가지고 오게
됩니다.
지금 위 예제코드에서는 단순히 선언과 동시에 바로 아래에서 써서
이런 어이없는 경우가 어딨냐고 반문하실지 모르겠지만.
실제 프로그램 로직이 복잡한 상황에서 변수 선언과 실제 인덱스를 접근해서
사용하는 위치의 차이가 큰 경우가 많기때문에 충분히 저러한 상황이
발생할 수 있습니다
따라서, 배열로 하나 포인터로 하나 사용자가 주의를 기울이지 않으면 똑같은
결과가 나타날거라 판단됩니다.

2) 메모리 할당과 해제에 소모되는 시간 소모가 없다.
배열에 의한 메모리 할당의 가장 큰 장점은 해제 과정이 필요없다라는거죠
하지만, 이러한 장점보다는 포인터와 비교했을때의 단점이 더 많다고 보여집니다.
가령,
배열의 경우, 굉장히 않은 메모리를 할당할경우, stack에 생성되는것이
아니라, heap에 생성되므로 포인터와 동일한 연산을 수행하며,
포인터의 경우 메모리가 필요한 그 시점에 사용되어지고 유지되는 반면에
배열의 경우 이미 해당 작업 공간을 할당해 있으므로 메모리 사용에
제약이 가져올 수 있습니다.
또한, 비록 배열이 메모리 해제과정이 없으나, 실제 내부코드적으로
해제과정에 필요한 OP 코드가 생성되므로, 기본 연산은 거의 같다고
보아야 하므로 의미가 없을거란 생각이 됩니다.

3) 인덱스 방식의 참조가 가능하므로 잘못된 포인터
참조에 의한 버그 가능성이 작다.
--> 이건 1번 내용과 중복되는 설명입니다.

4) 시스템의 메모리를 효율적으로 사용할수 있다.
--> 2번 설명에서 밝혔듯이 전혀 효율적이지 않습니다.

5) 참조 속도가 매우 빠르다.
--> 속도가 빠른것은 인정할 수 없습니다.
어떠한 기준에 의해서 이러한 결과가 나왔는지 궁금합니다.

6) 프로그램 소스 코드가 직관적이다.
( 포인터 참조 연산자가 얼마나 어려운 코드 형식을
작는지 다들 아시죠? )


배열역시 포인터 입니다.
앞서 강좌에서 배열의 중요성을 이야기하는 관점이 잘못된 포인터 연산에
따른 문제점이 크기 때문이라고 하셨는데.
이때문에, 포인터 사용의 이점을 버린다는것은 잘못된것이라 판단됩니다.
포인터에 대한 명확한 이해를 하고 사용해야 하며,
또한, 문제점에 대해서 충분한 검증을 거치는 방법
가령, leak tracer나, memory profiler를 이용해서 테스트 & 검증해나가는
방법이 바람직하다 보여집니다.


6번에서 들으신 예를 보면
로컬변수의 장점이
"선언이 간단하고 할당에 걸리는 시간이 없다는 것입니다."라고 하셨는데
무엇과의 시간을 비교해서 할당에 걸리는 시간이 없다라는 의미인지요?
할당에 걸리는 시간은 전역변수나 로컬변수가 항상 같지요.
물론, 함수의 호출이 빈번할 수록 생성 해제의 과정이 반복되는점은
있지만, 변수 선언하나만 놓고 보았을때 같습니다.

그리고 아래에 예제 코드를 적어서 문제될만한 소지를 적어 주셨는데
이것은, 로컬변수의 문제점이 아닙니다.
Boundary check를 하지 않아 memory overflow가 나는겁니다.
이러한 문제점은 오래전부터 있어서 주로 해킹에 많이 이용되었는데.
이를 보완하기 위해,
strcpy 함수나 sprintf 함수같은경우 권고하지 않고
사이즈를 넘겨줄 수 있는 strncpy 함수나 snprintf함수를 사용하길
권고합니다. (리눅스 맨 페이지를 참고하면 자세한 사항을 알 수 있습니다.)


이상입니다.
고수님의 강좌글에 대해서 하수가 이런저런 토를 달아서 죄송합니다.

조병완
http://www.korone.net
[ 이글에 답장 | 본문에 답장 ]

답장 유영창 (2004년 09월 18일 오후 09:32)
3. 배열을 사용하라에서 장점에 대해서 열거해 주셨는데요.
1) 메모리 할당과 해제와 관련된 버그가 없다.
--> 버그가 없다고 말할 수 없습니다.

인정합니다 버그가 없다니
이런 실수를 .... 용서하십시오..


2) 메모리 할당과 해제에 소모되는 시간 소모가 없다.

배열에 의한 메모리 할당의 가장 큰 장점은 해제 과정이 필요없다라는거죠

--> 이 부분 역시 인정...


하지만, 이러한 장점보다는 포인터와 비교했을때의 단점이 더 많다고 보여집니다.

--> 인정 하기 힘듭니다.

이건 개발자 스킬과 연관이 있읍니다.
제가 이글을 쓴 목적중 하나가 개발 과정에 실수를 줄이는 부분입니다.
또한 장기간 사용되는 부분 때문입니다.

포인터를 쓰지 말라는 것이 아닙니다.

할당과 해제보다는 가급적 배열을 쓰기를 권장한다는 말입니다.

즉 프로그램머가 프로그램이 복잡할수록 할당과 해제과정에서
실수를 많이 합니다.

그래서 많은 디버깅 툴들이 주로 메모리 릭에 관련된 부분을 점검하는것으로 압니다.

할당과 해제에서 버그가 없는 분들은 이미 닳고 닳은 분들입니다.
그런분들은 이미 코드 작성시에 방어적인 코드로 작성합니다.
이부분만큼은 당해봐야 아는데
배열과 같은 부분은 사전 할당이 되기 때문에 손해가 적습니다.
특히 초보자들에게는

제가 보기에는 조병완씨는 고수로 보입니다. ^^

3) 인덱스 방식의 참조가 가능하므로 잘못된 포인터

이 부분은 조금 더 세밀하게 이야기 하면

함수에 매개변수로 인자를 넘길때 포인터를 많이 사용합니다.
그런데 이 포인터 참조 방식으로 넘길 경우에는
범위 초과에 대한 처리를 검증할 방법이 없는 경우가 많습니다.
그러나 인덱스로 넘기면 디버깅 과정에서 추적도 용이하고
검증 함수를 사용할수 있읍니다.
제가 이야기 한것은 이런 관점에서 인덱스를 사용하기를
권장하는 것입니다. 그리고 가급적 매크로 상수를 사용하기를
권하는 것이지요..

4) 시스템의 메모리를 효율적으로 사용할수 있다.

꺼꾸로 임베디드 시스템은 한정된 메모리를 가지고 있읍니다.
다시 말씀드리면 시스템 사양이 결정될 때 역계산에 편할수 있읍니다.

예를 들어

나는 이 시스템에 300명까지를 처리할수 있도록 하겠다와
나는 이 시스템에 상황에 따라서 300명에서 600명까지 처리하겠다는
그 구현방식에 따라서 버그 가능성은 매우 달라집니다.

이건 제 경험이기 때문에 다른 경험을 하시는 경우라면 해당되지 않겠지만
저는 프로그램을 작성할 때

ptrData = malloc( 100 );
if( ptrData == NULL )
{

}

하는 문장에서 많은 갈등을 합니다.

에러가 나면 어떻게 할까요?

그냥 프로그램을 종료하게 할까요?

동작중인 상태에서 무정전에 이런 처리 상황은 조금 불편하죠...
꺼꾸로

처음부터 모든 할당을 하고 시작한다는 조건이라면
저역시 말씀하신 의견에 찬성합니다.


5) 참조 속도가 매우 빠르다.
--> 속도가 빠른것은 인정할 수 없습니다.

실수 ( 헤헤 ) 제가 왜 이런말을 썼는지 다시 한번 생각해 보겠읍니다.
(분명히 이유가 있었는데 쩝 )

앞서 강좌에서 배열의 중요성을 이야기하는 관점이 잘못된 포인터 연산에
따른 문제점이 크기 때문이라고 하셨는데.
이때문에, 포인터 사용의 이점을 버린다는것은 잘못된것이라 판단됩니다.
포인터에 대한 명확한 이해를 하고 사용해야 하며,
또한, 문제점에 대해서 충분한 검증을 거치는 방법
가령, leak tracer나, memory profiler를 이용해서 테스트 & 검증해나가는
방법이 바람직하다 보여집니다.

--> 장기적으로는 저 역시 동의합니다.
하지만 저같이 게으른 사람은 그것을 느끼고 회피하는데 너무 많은 시간과
너무 많은 고생을 했읍니다.

배열로도 가능하면 그냥 배열로 처리하라는 것이 요즘 제 생각입니다.


그외 지적사항 역시 겸허히 받아 들입니다. ^^
[ 이글에 답장 | 본문에 답장 ]

답장 익명 (2004년 09월 20일 오전 10:59)
음... 로컬변수는.. 그냥 스택포인터만 조정하면 되니깐 빠른거 아닐까요? ㅡ,ㅡㅋ

하긴 생각해보니 임베디드 시스템에서 굳이 메모리를 놀릴필요가 없겠네요. ㅡ,ㅡ;

무의식적으로 보다 일반적이고 범용적인 코드를 짜려고 굳이 애써왔던 기억이 ㅡ,ㅡ;

이래서 쟁이라는 소릴 듣나. ㅡ,ㅡ;;;

아무튼 좋은 글 좋은 의견 잘 보았습니다. 확실히 크게 배운 것은 있습니다.
[ 이글에 답장 | 본문에 답장 ]

답장 korone.net (2004년 09월 20일 오후 12:12)
일반적으로는 그렇죠^^

로컬변수의 경우, 스택포인터만 조정하면 되니까,

하지만, 만약 스택에 담을 수 있는 변수의 길이를 넘게된다면 이야기가

틀려집니다.

만약, char tmp[100000];

이렇게 선언한다면, tmp 변수는 stack에 생기지 않고 heap에 생기게 됩니다.

임베디드 라서 굳이 메모리를 놀릴필요가 없겠다라는 말이

임베디드의 경우, 단가가 생명인데...

만약, 메모리를 64메를 올려놓고 프로그램을 짤때...

프로그램 메모리 사용을 효율적으로 해서 32메가로도 처리할 수 있다라면

이는 제품 단가를 줄일 수 있는 효과적인 방법이라 될 수 있다고 봅니다.

물론 강좌를 쓰신 유영창님께서도 밝혀주셨지만.

초보자를 위하고 어떠한 효율성보다도 안전성이 우선시 되는 임베디드

상황에서 나름대로 일리도 있다고 봅니다.

조병완
http://www.korone.net
[ 이글에 답장 | 본문에 답장 ]

답장 익명 (2005년 01월 18일 오후 03:43)
5) 참조 속도가 매우 빠르다.
--> 속도가 빠른것은 인정할 수 없습니다.

보통 프로그래밍시 동적으로 메모리를 할당 받는 경우는
대부분 리스트 이용을 위해서인데 배열이 리스트의 참조 속도보다 월등히
빠르다는 것을 인정할 수 없다는 것은 이해할 수 없군요.
배열의 참조는 상수 타임이며 리스트이 참조 시간은 빨라야 nlog(n)
time complexity를 갖게됩니다.
[ 이글에 답장 | 본문에 답장 ]

답장 익명 (2004년 09월 26일 오후 10:05)
전역변수보다 지역변수는 매번 비역변수의 공유영역을 지우고 다시 쓰는
절차가 컴파일러의 서브루틴 생성시 마다 발생하므로 결국 실행속도는
전역변수보다 지역변수가 느려집니다.
[ 이글에 답장 | 본문에 답장 ]

답장 익명 (2006년 12월 28일 오전 10:18)
포인터 사용에는 신중해야 합니다 에러의 주범 맞아요

Windows 에서 GTK+설치하고 갖고 놀기(퍼온글)

참고 사이트(http://cafe.naver.com/themaxkor.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=29)

ms-window
에서 dev-c++(devcpp) gtk+ 으로 프로그래밍 하기


c도 초보고 gtk+도 초보입니다. 하지만 셋팅을 하는 동안의 과정을 간략하게 정리하고자 합니다.
dev-c++은 설치되었다는 전제하에서 설명하겠습니다. (참고로 dev-c++의 주소는 http://bloodshed.net/index.html 입니다.)
dev-c++을 한글환경에서 설명할 것이기 때문에 한글환경이 아니라면, 메뉴의 ‘tools’ -> ‘environment options’ 에서 ‘interface’ 탭에 있는’ language’에서 ‘korea(한국어)’를 선택하세요.

1. gtk+ 설치를 위한 사전 작업
gtk+는 다른 라이브러리들과 의존성을 가지기 때문에 사전에 의존성을 가지는 라이브러리들을 설치해주어야 합니다.

라이브러리 목록은 다음과 같습니다.

  • glib

  • atk

  • pango

  • zlib

  • libpng

  • libpixman

  • cairo

7개의 라이브러리를 먼저 설치해 주어야 합니다. 마지막에 있는 cairo는 바로 위 두개(libpng, libpixman)에 의존성을 가지므로 cairo를 설치하기 전에 두개의 패키지를 먼저 설치하여야 하고, libpngzlib에 의존성을 가지므로 zlib를 먼저 설치해야 합니다. 일단 이부분은 접어두고 총 7개의 라이브러리를 먼저 설치해야 합니다. 아래 부분에서 자세히 설명을 할테니 넘어갑시다.

설치를 위해 dev-c++에서 제공하는 패키지 업데이를 진행하면 됩니다.

메뉴의 ‘도구’ -> ‘프로그램 업데이트’를 선택하면 아래와 같이 하나의 창이 뜹니다.

select devpak server’에서 ‘devpaks.org community devpaks’를 선택합니다.

그리고, 아래의 ‘check for updates’를 클릭합니다. 그려면 프로그래스바가 진행되고 ‘available updates list’에 업데이트할 수 있는 목록이 작성됩니다. 그리고, ‘check for updates’는 ‘download selected’로 바뀌게 됩니다.

설치순서는 아래와 같습니다.

   1.
zlib
   2.l
ibpixman, libpng
   3.
atk, cairo, glib, pango


순서대로 각 항목에 체크를 하고 ‘download selected’를 눌러주면 됩니다. (한 항목에 두개 이상의 패키지가 있으면, 체크란에 두개 다 체크하고 ‘download selected’를 눌러주면 됩니다.) 간단한 대화창과 함께 설치가 완료되면, 이제 준비작업은 끝났습니다. (사실 준비작업이라 말하기는 좀 그렇지만...)


2.gtk+
설치
이제 gtk+를 체크하고 설치하면 됩니다.(download selected)
이제 gtk+를 프로그래밍을 하기 위한 준비는 끝났습니다. 그런데, 실행파일을 만들기 위해서는 dll 파일들이 필요합니다. 이 파일들을 gimp에서 다운받을 수 있습니다.

윈도우를 위한 gimp의 주소는 http://gimp-win.sourceforge.net/ 입니다. 다운로드 주소는 http://gimp-win.sourceforge.net/stable.html 입니다. 그런데...

gtk+ 2 runtime environment를 다운받아야 하는데, 목록이 두개가 있습니다.

저는 xp환경이라 위에 있는 (version 2.10.6 for windows 2000 and newer) 다운받고 압축풀고 설치를 시작하면 간단한 대화상자가 나오고, 설치를 하면됩니다.


3.gtk+ 프로그래밍 해보기
메뉴에서 ‘파일’ -> ‘새로 만들기’ -> ‘프로젝트’ 를 선택합니다. 그러면 아래와 같이 창이 뜹니다.
basic 탭에서 gtk+ application, 프로젝트명을 적당히 적어주고 언어는 c를 선택합니다. 그리고 확인을 누르면 gtk+ 프로그래밍을 할 수 있게 소스창이 구성됩니다.

메뉴에서 ‘실행’ -> ‘컴파일’ 그러면 컴파일이 되고 실행파일이 만들어집니다. 이제 gtk 프로그램이 완성되었습니다.

이제 http://gtk.org에서 튜토리얼을 보면서 이것저것 공부하면 됩니다. ^^
(그런데, ‘실행’ -> ‘실행’을 눌러주면 먹통일 때가 있습니다. 소스를 컴파일 한 곳에서 가서, 실행파일을 어떤 dll파일이 필요하다는 에러메시지가 뜹니다. 이때를 위해 조금전에 gtk+ runtime environment를 설치한 것입니다. ‘program files’- > ‘common files’ -> ‘gtk’ -> ‘2.0’ -> ‘bin’ 폴더에 보면 많은 dll파일들이 있습니다. 그곳에서 dll 파일을 찾아 실행파일이 있는 곳에 복사하고 실행파일을 더블클릭하면 이제 실행이 됩니다. 패스를 걸어주셔도 됩니다.)

Volatile이란?(퍼온글)

긁어온 사이트 (http://blog.naver.com/itsatan?Redirect=Log&logNo=100041107893)

volatile 키워드는 const 와 함께 변수의 성질을 바꾸는 역할을 하는데 이 둘을 묶어 cv 지정자라고 한다. const에 비해 상대적으로 사용 빈도가 지극히 낮으며 이 키워드가 꼭 필요한 경우는 극히 드물다. 어떤 경웅 volatile 이 필요한지 다음 코드를 보자.


int i;

double j;

for(i=0; i<100; i++) {

   j =sqrt(2.8) + log(3.5) + 56;

   // do something

}


이 코드는 루프를 100번 실행하면서 어떤 작업을 하는데 루프 내부에서 j에 복잡한 연산 결과를 대입하고 있다. j값을 계산하는 식이 조금 복잡하지만 제어 변수 i값을 참조하지 않기 때문에 i 루프가 실행되는 동안 j의 값은 상수나 마찬가지이며 절대적으로 변경되지 않는다. 루프 실행중에는 상수이므로 이 값을 매 루프마다 다시 계산하는 것은 시간 낭비이다. 그래서 제대로 된 컴파일러는 이 루프를 다음과 같이 수정하여 컴파일한다.


j =sqrt(2.8) + log(3.5) + 56;

for(i=0; i<100; i++) {

   // do something

}


i 의 값을 계산하는 식을 루프 이전의로 옮겨서 미리 계산해 놓고 루프 내부에서는 j값을 사용하기만 했다. 어차피 루프 내부에서 j값이 바뀌는 것이 아니므로 이렇게 코드를 수정해도 원래 코드와 완전히 동일한 동작을 할 것이다. 똑똑한 컴파일러는 프로그래머가 코드를 대충 짜 놓아도 속도를 높이기 위해 자동으로 최적화를 하는 기능을 가지고 있으며 이런 암묵적인 최적화 기능에 의해 프로그램의 성능이 향상된다.

그렇다면 위 두 코드가 정말로 완전히 동일할가 으심을 가져보자. j는 분명히 루프 내부에서 상수이므로 미리 계산해 놓아도 아무 문제가 없음이 확실하다. 그러나 아주 특수한 경우 최적화된 코드가 원래 코드와 다른 동작을 할 경우가 있다. 어떤 경우인가 하면 프로그램이 아닌 외부에서 j의 값을 변경할 때이다.

도스 환경에서는 인터럽트라는 것이유닉스 환경에서는 데몬, 윈도우즈 환경에서는 서비스 등의 백그라운드 프로세스가 항상 실행된다. 이런 백그라운드 프로세스가 메모리의 어떤 상황이나 전역변수를 변경할 수 있으며 같은 프로세스 내에서도 스레드가 여러 개라면 다른  스레드가 j의 값을 언제든지 변경할 가능성이 있다. 또한 하드웨어에 의해 전역 환경이 바뀔 수 도있다. 예를 들어 위 코드를 실행하는 프로세스가 두 개의 스레드를 가지고 있고 다른 스레드에서 어떤 조건에 의해 전역변수 j값(또는 j에 영향을 미치는 다른 값) 을 갑자기 바꿀 수도 있다고 하자. 이런 경우 루프 내부에서 매번 j값을 다시 계산하는 것과 루프에 들어가깆 너에 미리 계산해 놓는 것이 다른 결과를 가져올 수 있다. i루프가 50회째 실행중에 다른 스레드가 j값을 바꾸어 버릴 수도 있는 것이다.

이런 경우에 쓰는 것이 바로 volatile 이다. 이 키워드를 변수 선언문 앞에 붙이고 컴파일러는 이 변수에 대해서는 어떠한 최적화 처리도 하지 않는다. 컴파일러가 보기에 코드가 비효율적이건 어쨋건 개발자가 작성한 코드 그대로 컴파일 한다. 즉 volatile 키워드는 "잘난척 하지 말고 시키는 대로 해" 라는 뜻이다. 어떤 변수를 다른 프로세스나 스레드가 바꿀 수도 있다는 것을 컴파잉ㄹ러는 알수 없기 때문에 전역 환경을 참조하는 변수에 대해서는 개발자가 volatile 선언을 해야한다. 위 코드에서 j 선언문 앞에 volatile 만붙이면 문제가 해결 된다.


volatile double j;


이 키워드가 반드시 필요한 상황에 대한 예제를 만들어 보이는 것은 굉장히 어렵다. 왜냐하면 외부에서 값을 바꿀 가능성이 있는 변수에 대해서만 이 키워드가 필요한데 그런 예제는 보통 크기가 아니기 때문에 잘 사용하지 않는 기워드이므로 여기서는 개념만 익혀 두도록 하자.

바이너리 rpm만들기(armv4l용)

rpm으로 풀고자 하는 파일들을 gzip으로 압축을 한다.
ex) mkdir medic-1.0.0
      cp medic_test/medic medic_test/libdfb_grp.so.0.0.0 medic-1.0.0
      cd medic-1.0.0
      ln -s libdfb_grp.so.0.0.0 libdfb_grp.so
      ln -s libdfb_grp.so.0.0.0 libdfb_grp.so.0
      cd ..
      tar cvfz medic-1.0.0.tar.gz medic-1.0.0
그리고 압축된 파일을 rpm만들고자 하는 딜렉토리로 옮긴다.
      cp medic-1.0.0.tar.gz /usr/src/booyo/SOURCES
이제 Spec파일을 만들차례다.
      cd /usr/src/booyo/SPEC
      vi medic-1.0.0.spec


%define version 0.0.1
%define name medic

Summary: Medical equipment
Name:  %{name}
Version: %{version}
Release: 1
Prefix:  /root
Group:  /root
Source:  %{name}-%{version}.tar.gz
BuildRoot: /var/tmp/rpm/%{name}-%{version}
License: GPL
URL:  http://www.etri.re.kr

%description

%prep
%setup
mkdir -p $RPM_BUILD_ROOT/root/medic
cp -af /usr/src/booyo/BUILD/%{name}-%{version}/%{name} $RPM_BUILD_ROOT/root/medic/%{name}
mkdir -p $RPM_BUILD_ROOT/lib
cp -af /usr/src/booyo/BUILD/%{name}-%{version}/libdfb_grp.so.0.0.0 $RPM_BUILD_ROOT/lib/libdfb_grp.so.0.0.0
mv /usr/src/booyo/BUILD/%{name}-%{version}/libdfb_grp.so $RPM_BUILD_ROOT/lib/libdfb_grp.so
mv /usr/src/booyo/BUILD/%{name}-%{version}/libdfb_grp.so.0 $RPM_BUILD_ROOT/lib/libdfb_grp.so.0
#ln -s $RPM_BUILD_ROOT/lib/libdfb_grp.so.0.0.0 $RPM_BUILD_ROOT/lib/libdfb_grp.so
#ln -s $RPM_BUILD_ROOT/lib/libdfb_grp.so.0.0.0 $RPM_BUILD_ROOT/lib/libdfb_grp.so.0

%files
%{prefix}/medic/%{name}
/lib/libdfb_grp.so.0.0.0
/lib/libdfb_grp.so.0
/lib/libdfb_grp.so

%clean
rm -rf $RPM_BUILD_ROOT

이제 모든 준비는 끝났다.
바이너리 RPM을 생성해 보자.
     cd /usr/src/booyo/SPEC
     rpmbuild -ba --target=armv4l medic-1.0.0.spec

이렇게 하면 /usr/src/booyo/RPMS/armv4l/medic-1.0.0.armv4l.rpm 이 생성됨을 알수 있다.
우리는 이제 이것을 배포하기만 하면 된다. (근데 아직 테스트를 해보지 않아서 잘될지 알수가 없구나~~)

참조 : http://www.coffeenix.net/doc/HOWTOs/html/RPM-HOWTO/RPM-HOWTO-7.html
         http://blog.naver.com/yong0872?Redirect=Log&logNo=70001568086

armv4l.rpm을 풀어라..

# rpm -Uvh gtk+-2.0.0-1.armv4l.rpm
이건 현재 host에 설치하라는 소리인데~~~~
rpm2cpio xxx.rpm | cpio -i --make-directories
or
rpm2cpio xxx.rpm | cpio -id

이런식으로 하면 현재 디렉토리에gtk+-2.0.0-1.armv4l.rpm 이 풀린다...
풀린 rpm을 이제 타겟보드에 포팅하면 된다. ^^

cpu 사용율 구하기(3) - top명령 이용

#include <stdio.h>

int main()
{
        FILE * tp;
        char lines[1024];
        int j=0;

        tp = popen("top -b -n1 | sed -n 3p | awk '{print $2}'", "r");

        while(fgets(lines, 1024, tp))
        {
                if (lines[j] != ' ') j++;
        }
        printf("%s\r\n", lines);
        pclose(tp);
        return 0;
}

CPU 사용율 구하기(1)

참조 : directfb예제중 df_cpuload.c 를 이용.
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <gtk/gtk.h>

#define SET_IF_DESIRED(x,y) do{  if(x) *(x) = (y); }while(0)
#define JT unsigned long

static void four_cpu_numbers(int *uret, int *nret, int *sret, int *iret);
static int get_load();
void* cpu_load(int t);


GThread *adc_insert_thrs;
GError *adc_error;


static void four_cpu_numbers(int *uret, int *nret, int *sret, int *iret)
{
     int       tmp_u, tmp_n, tmp_s, tmp_i;
     static JT old_u, old_n, old_s, old_i, old_wa, old_hi, old_si;
     JT        new_u, new_n, new_s, new_i, new_wa = 0, new_hi = 0, new_si = 0;
     JT        ticks_past; /* avoid div-by-0 by not calling too often :-( */
     char      dummy[16];
     FILE     *stat;

     stat = fopen ("/proc/stat", "r");
     if (!stat)
          return;

     if (fscanf (stat, "%s %lu %lu %lu %lu %lu %lu %lu", dummy,
                 &new_u, &new_n, &new_s, &new_i, &new_wa, &new_hi, &new_si) < 5)
     {
          fclose (stat);
          return;
     }

     fclose (stat);

     ticks_past = ((new_u + new_n + new_s + new_i + new_wa + new_hi + new_si) -
                   (old_u + old_n + old_s + old_i + old_wa + old_hi + old_si));
     if (ticks_past) {
          tmp_u = ((new_u - old_u) << 16) / ticks_past;
          tmp_n = ((new_n - old_n) << 16) / ticks_past;
          tmp_s = ((new_s - old_s) << 16) / ticks_past;
          tmp_i = ((new_i - old_i) << 16) / ticks_past;
     }
     else {
          tmp_u = 0;
          tmp_n = 0;
          tmp_s = 0;
          tmp_i = 0;
     }

     SET_IF_DESIRED(uret, tmp_u);
     SET_IF_DESIRED(nret, tmp_n);
     SET_IF_DESIRED(sret, tmp_s);
     SET_IF_DESIRED(iret, tmp_i);

     old_u  = new_u;
     old_n  = new_n;
     old_s  = new_s;
     old_i  = new_i;
     old_wa = new_wa;
     old_hi = new_hi;
     old_si = new_si;
}
#undef JT

static int
get_load()
{
     static int old_load = 0;

     int u = 0, n = 0, s = 0, i, load;

     four_cpu_numbers( &u, &n, &s, &i );

     //load = u + n + s;
     load = u + n + s + i;

     //old_load = (load + load + load + old_load) >> 2;
     old_load = ((u+n+s)*100) / (load);

     //return old_load >> 10;
     return old_load;
}

void* cpu_load(int t)
{
 int load;
 while(1){
  usleep(100000);
  load = get_load();
  printf("cpu load = %d\n",load);
 }
 
 return (void*)0;
}

int main(int argc, char** argv)
{
 int load;
 g_thread_init(NULL);
 adc_insert_thrs = g_thread_create((GThreadFunc)cpu_load,0,TRUE,&adc_error);
 
 g_thread_join(adc_insert_thrs );
 /*
 while(1){
  usleep(100000);
  load = get_load();
  printf("cpu load = %d\n",load);
 }
 //*/
 
  return 0;
}

cpu load에 관한 자료

1 CPU load
2 --------
3
4 Linux exports various bits of information via `/proc/stat' and
5 `/proc/uptime' that userland tools, such as top(1), use to calculate
6 the average time system spent in a particular state, for example:
7
8     $ iostat
9     Linux 2.6.18.3-exp (linmac)     02/20/2007
10
11     avg-cpu:  %user   %nice %system %iowait  %steal   %idle
12               10.01    0.00    2.92    5.44    0.00   81.63
13
14     ...
15
16 Here the system thinks that over the default sampling period the
17 system spent 10.01% of the time doing work in user space, 2.92% in the
18 kernel, and was overall 81.63% of the time idle.
19
20 In most cases the `/proc/stat' information reflects the reality quite
21 closely, however due to the nature of how/when the kernel collects
22 this data sometimes it can not be trusted at all.
23
24 So how is this information collected?  Whenever timer interrupt is
25 signalled the kernel looks what kind of task was running at this
26 moment and increments the counter that corresponds to this tasks
27 kind/state.  The problem with this is that the system could have
28 switched between various states multiple times between two timer
29 interrupts yet the counter is incremented only for the last state.
30
31
32 Example
33 -------
34
35 If we imagine the system with one task that periodically burns cycles
36 in the following manner:
37
38 time line between two timer interrupts
39 |--------------------------------------|
40 ^                                    ^
41 |_ something begins working          |
42                                       |_ something goes to sleep
43                                      (only to be awaken quite soon)
44
45 In the above situation the system will be 0% loaded according to the
46 `/proc/stat' (since the timer interrupt will always happen when the
47 system is executing the idle handler), but in reality the load is
48 closer to 99%.
49
50 One can imagine many more situations where this behavior of the kernel
51 will lead to quite erratic information inside `/proc/stat'.
52
53
54 /* gcc -o hog smallhog.c */
55 #include <time.h>
56 #include <limits.h>
57 #include <signal.h>
58 #include <sys/time.h>
59 #define HIST 10
60
61 static volatile sig_atomic_t stop;
62
63 static void sighandler (int signr)
64 {
65      (void) signr;
66      stop = 1;
67 }
68 static unsigned long hog (unsigned long niters)
69 {
70      stop = 0;
71      while (!stop && --niters);
72      return niters;
73 }
74 int main (void)
75 {
76      int i;
77      struct itimerval it = { .it_interval = { .tv_sec = 0, .tv_usec = 1 },
78                              .it_value = { .tv_sec = 0, .tv_usec = 1 } };
79      sigset_t set;
80      unsigned long v[HIST];
81      double tmp = 0.0;
82      unsigned long n;
83      signal (SIGALRM, &sighandler);
84      setitimer (ITIMER_REAL, &it, NULL);
85
86      hog (ULONG_MAX);
87      for (i = 0; i < HIST; ++i) v[i] = ULONG_MAX - hog (ULONG_MAX);
88      for (i = 0; i < HIST; ++i) tmp += v[i];
89      tmp /= HIST;
90      n = tmp - (tmp / 3.0);
91
92      sigemptyset (&set);
93      sigaddset (&set, SIGALRM);
94
95      for (;;) {
96          hog (n);
97          sigwait (&set, &i);
98      }
99      return 0;
100 }
101
102
103 References
104 ----------
105
106 http://lkml.org/lkml/2007/2/12/6
107 Documentation/filesystems/proc.txt (1.8)
108
109
110 Thanks
111 ------
112
113 Con Kolivas, Pavel Machek
prev 1 2 next