XPCOM

From MDC


An Introduction to XPCOM

A five part tutorial from IBM developerWorks: Part I, Part II, Part III, Part IV, Part V

XPCOM is a cross platform component object model, similar to Microsoft COM. It has multiple language bindings, letting the XPCOM components be used and implemented in JavaScript, Java, and Python in addition to C++. Interfaces in XPCOM are defined in a dialect of IDL called XPIDL.

XPCOM itself provides a set of core components and classes, e.g. file and memory management, threads, basic data structures (strings, arrays, variants), etc. The majority of XPCOM components is not part of this core set and is provided by other parts of the platform (e.g. Gecko or Necko) or by an application or even by an extension.


Documentation

Core XPCOM documentation

XPCOM API Reference
Documentation of core components, interfaces, and functions provided by XPCOM.
String, hashtable, and array guides
Using the basic data structures.
XPCOM Glue
The XPCOM Glue allows using XPCOM utility functions and classes, without having a dependency on unfrozen parts of XPCOM (xpcom_core.{dll,so,dylib}).

Creating XPCOM components

Creating XPCOM Components
This book provides a tutorial about building an XPCOM component that controls browsing behavior.
How to Build an XPCOM Component in Javascript
Quick-start guide for writing JavaScript components
How to Build an XPCOM Component in C++
Creating Custom Firefox Extensions with the Mozilla Build System . Don't let extensions in the title fool you - the article is mainly about binary components
Linux and Windows HowTo
Windows HowTo
Redux
OS/X HowTo

Miscellaneous

Using XPCOM in JavaScript without leaking
Using XPCOM in JavaScript (also known as XPConnect) is an environment where memory management issues are not obvious. Despite this, it's easy to write JavaScript code that leaks, because some of the objects you're dealing with are reference-counted behind the scenes.

View All...

Community

Tools

JavaScript Component Wizard
Visual C++ Component Wizard

Related Topics

Language Bindings, JavaXPCOM, XPConnect, PlXPCOM

'Development > 기타' 카테고리의 다른 글

[GPS]GPS모듈 구현중  (0) 2009.12.14
static 용어의 개념 및 성질  (0) 2009.10.25
[펌]회사에서 네이트 온 하기!!  (0) 2008.06.13
Creating Application with mozila  (0) 2008.05.13
xpcom  (0) 2008.05.11
Posted by 까 치
,

1. 정적 지속성(static storage duration):static 객체들은 일단 한번 생성이 되면 프로그램이 종료 될 때까지 유지.
즉 객체의 소멸 시점이 scope에 영향 받지 않고 항상 일정함.
2. 유일성(singleton): 어떤 모듈 단위 (function, class, file)에서든지 static 객체는 단 한번만 생성
3. 내부 연결성(internal linkage):전역 static 객체나 함수는 link단계에서 외부 바인딩이 일어나지 않음.
즉, 외부 파일에서는 내부 전역 static 객체/함수를 참조하거나 호출할 수 없음

Binding

static
에 대해서 이해하기 위해서는 우선 binding이라는 개념을 이해하여야 합니다. 바인딩이라는 단어 역시 다양한 의미로 사용되는 용어인데 프로그래밍 언어에서 말하는 바인딩이란 어떤 심볼(변수나 함수)의 속성을 결정짓는 것을 의미합니다.

int main()
{
  int x;
  x = 3;
  return 0;
}

위와 같은 C 소스가 있을 때 이것을 컴파일러가 컴파일 하게 되면 우선 컴파일러는 int x; 라는 선언문을 보고서 x라는 이름의 변수를 자신의 심볼 테이블에 기록을 하며 이 때 그 변수의 속성(type, scope, value )을 저장하게 됩니다. 그리고 그 다음에 있는 x = 3; 이라는 구문에서 x라는 변수가 자신의 심볼 테이블에 있는지 살펴보고, 있으면 그 속성을 참고하여 해당 구문의 타입 체크와 같은 문법 검사를 수행하게 됩니다.(물론 x가 심볼 테이블에 없다면 그런 변수를 찾을 수 없다는 컴파일 에러를 발생시킵니다.)

이제 좀 더 복잡한 예를 살펴보겠습니다.

/// test1.cpp
#include <iostream>
  int global; // ---> 1)
  void extern_fun(int a);
  int main() 
  {
 
  global = 5; // ---> 2)
 
  extern_fun(3);
 
  std::cout << global << 'n';
   
return 0;
  }

 

/// test2.cpp
extern int global;  // ---> 3)
void extern_fun(int a)
{
  global += a;      // ---> 4)
  }

 위 소스에서는 global이라는 전역 변수를 두 개의 파일에서 참조하고 있습니다. 위에서 언급했듯이 컴파일러는 변수 선언문이 나타나면 해당 변수를 심볼 테이블에 등록하고 이후에 변수를 사용하는 구문을 만날 때마다 해당 변수가 자신의 심볼 테이블에 등록되어 있는지 살펴보고 그 정보에 따라 문법 검사를 수행하게 됩니다.

 그런데 컴파일러는 항상 파일 단위로 컴파일을 수행하며 새로운 파일을 컴파일 할 때 이전에 이미 컴파일 한 파일에 대한 정보 같은 것들은 따로 기록하지 않습니다. , 항상 새로운 마음가짐으로 각각의 파일들을 독립적으로 컴파일 하게 됩니다.

그래서 위의 경우 test1.cpp를 먼저 컴파일 했다고 해서 test2.cpp에서 global변수를 사용하는 구문을 컴파일 할 때 test1.cpp에서 만든 심볼 테이블을 참조하지는 않습니다.

 때문에 두 개 이상의 파일에서 같은 전역 변수를 참조하게 되면 그런 사실을 미리 소스 코드를 통해 컴파일러에게 알려줘야 합니다. 만약 그렇지 않으면 test2.cpp에서 4)구문을 컴파일 할 때 global이라는 변수가 선언되어 있지 않다 라고 에러를 발생시키거나 혹은 test1.cpp test2.cpp global변수를 별개의 변수로 취급하게 될 것입니다.

 그러므로 프로그래머는 사전에 test1.cpp test2.cpp에서 사용하는 global 변수가 동일한 것이다라는 사실을 컴파일러에게 알려주게 되는데 위의 소스에서 3)에 있는 extern은 바로 그런 역할을 수행하는 키워드입니다.

extern의 의미는 '해당 변수는 외부에서 참조하여 사용하겠다'라는 뜻입니다. 만약 3)이 없다면 test2.cpp를 컴파일 할 때 4)에서 global이라는 변수를 찾을 수 없다는 에러가 발생할 것이며, 3)에서 extern이 없다면 test1.cpp test2.cpp global은 별개의 변수로 취급되어 프로그램 결과가 8이 아니라 5가 나오게 될 것입니다.

 그렇다면 컴파일러는 각각의 파일을 컴파일 할 때 다른 파일의 심볼 값들을 참조하지 않음에도 불구하고 어떻게 전역 변수를 공유할 수 있을까요? 그것은 바로 링크 과정이 있기 때문에 가능합니다.

컴파일 시 컴파일러는 전역 변수나 함수에 대한 참조/호출을 직접적인 어셈블리어 구문으로 변환하는 것이 아니라(다시 말하면 해당 변수의 메모리 주소나 함수의 코드 주소로 바로 변환하지 않고) 특정한 이름을 부여하여 해당 이름의 전역 변수나 함수에 대한 참조/호출 구문으로 바뀌게 되는 것입니다.

 예를 들어 위의 소스의 경우에 test2.cpp에서 참조하는 global이 외부에서 참조되는 변수이다라는 사실을 컴파일 한 목적 코드(object code)에 기록해 두면 링커는 해당 변수와 동일한 이름으로 정의된 파일을 검색하고 test1.cpp에서 일치된 이름을 발견하면 해당 전역 변수 참조 구문들을 올바른 속성(상대 주소)값으로 바꾸게 됩니다.

 이렇게 어떤 변수나 함수의 속성값을 결정짓는 과정을 바인딩이라고 합니다. 그리고 위의 경우처럼 링크 과정에서 그런 속성값을 결정짓는 것을 링크 바인딩(link binding), 혹은 동적 바인딩(dynamic binding)이라 합니다.

정적 바인딩에 의해 정의되는 변수나 함수는 링크 과정 이전에 이미 속성이 결정되기 때문에 아래와 같은 특징을 가지게 됩니다.  

1. 정적 바인딩 변수는 extern 키워드를 통해 외부 파일에서 참조가 불가능하다.
2. 정적 바인딩 함수는 외부 파일에서 호출이 불가능하다.

static 전역 변수나 함수를 사용해 보신 분들은 아시겠지만 위의 특징은 바로 static 전역 변수와 함수의 특징과 일치합니다. , static 키워드는 해당 전역 변수나 함수를 컴파일러가 정적으로 바인딩을 하도록 프로그래머가 '지시'하는 역할을 합니다.

컴파일 타임 바인딩을 정적(static) 바인딩이라고 말하는 이유는 아마도 한번 컴파일 시에 속성이 결정되고 나면 '변하지 않는다' 라고 하는 불변성 때문이 아닌가 추측됩니다. 어쨌든 이런 특징을 가지고 있기에 static 변수는 독특한 성질을 지니고 있습니다.

 아래의 예제는 초보자가 흔히 하게 되는 실수입니다.

 /// header.h
int global;

/
// a.cpp
#include <iostream>
#include "header.h"
void b_func();
int main()
{
  global = 3;
  b_func();
  std::cout << global << std::endl;
  return 0;
}

/// b.cpp
#include "header.h"
void b_func()
{
  global = 5;
}

위 소스는 정상적으로 컴파일 됩니다.

앞에서 언급했듯이 컴파일러는 파일 별로 독립적으로 컴파일을 수행합니다.
따라서 a.cpp 컴파일 시 header.h에 있는 global 선언문을 통해 global 변수를 하나 생성하여 3을 할당합니다.
또한 b.cpp 역시 header.h에 있는 global 선언문을 통해 global 변수를 하나 생성하여 5를 할당합니다. 따라서 둘 다 적법한 소스입니다.

 하지만 링크 과정에서 링커는 a.cpp b.cpp가 동일한 이름(global)의 전역 변수를 두 개 생성한 것을 보고 링크 에러를 발생시킵니다.
(
이 때 발생하는 에러는 redefinition error입니다. 아마 초보자들이 가장 많이 접하는 에러 중 하나일 것입니다.)

 그러면 위 소스를 아래와 같이 수정해 보겠습니다.

 // header.h
static int global; /// int global static 전역 변수로 수정

// a.cpp
///원 소스와 동일...

// b.cpp
/// 원 소스와 동일....

이렇게 수정하면 정상적으로 컴파일 및 링크가 수행됩니다. 왜냐하면 static 전역 변수는 컴파일 시점에 바인딩이 완료되고 따라서 링크 과정에서 해당 변수에 대한 바인딩 처리를 하지 않으므로 중복 정의를 검사하지 않기 때문입니다.

그러나 대신 a.cpp b.cpp에서 사용하는 global 변수는 완전히 별도의 객체로 처리됩니다. 따라서 위 프로그램에서 a.cpp가 출력하는 global값은 - b_func()함수를 호출함으로써 바뀌는 값- 5가 아니라 - 원래 a.cpp에서 할당한 값 - 3이 됩니다.

, a.cpp b.cpp에서 사용하는 global 전역 변수는 사실 이름만 똑같을 뿐 별개로 취급되는 다른 변수가 됩니다.
(
따라서 이런 식의 사용은 프로그래머에게 혼란을 줄 뿐이며 버그를 야기시키는 좋지 않은 코딩 방식입니다. 그러므로 static 전역 변수를 선언할 때는 항상 header 파일이 아닌 c/cpp 파일에 해줘야 합니다.)

어쨌든 static 전역 변수는 항상 해당 변수가 선언된 파일 내부에서만 참조가 가능합니다. 그리고 이것은 static 전역 함수 역시 마찬가지이며 static으로 선언된 전역 함수는 외부 파일에서는 사용이 불가능합니다.(만약 외부에서 호출하려고 하면 링크 에러가 발생합니다.) 

때문에 보통 C프로그래머들은 외부 파일에서 사용할 필요가 없는 혹은 외부에서 사용하기를 원치 않는 함수들은 static으로 정의하곤 합니다. 이렇게 함으로써 이름 중복에 의한 혼란 등을 피할 수 있는 부수적인 장점을 얻을 수도 있습니다.
(
C++에서는 namespace class가 생기면서 이런 장점이 많이 사라졌습니다.)

그리고 이렇게 파일 내부에서만 참조 가능한 static 의 성질을 internal linkage(내부 연결성)이라고 부릅니다.

 

storage duration and scope

다 아시는 이야기 하나 해보겠습니다. 변수는 관점에 따라 다양하게 분류가 될 수 있습니다. 우선 공간(scope)범위에 따라 전역 변수와 지역 변수로 분류가 가능합니다. 그리고 메모리 할당 주체에 따라 정적 할당 변수와 동적 할당 변수로 분류할 수도 있습니다. 또한 메모리 할당 위치에 따라 스택(stack) 변수와 정적 영역 변수, 동적 영역(heap) 변수로 나눌 수 있으며 그 외에도 정적(static)변수, 상수(const)변수, 포인터 변수 등등 여러 종류로 구분이 가능합니다.

게다가 이러한 분류는 복합적으로 정의될 수 있습니다. 가령, 정적 전역 변수(static global variable), 정적 지역 변수(static local variable), 전역 상수 변수(global const variable) 등등이 있습니다.

심지어 어떤 분류 정의는 서로 밀접한 관계(혹은 동등한 의미)를 가집니다. 스택 변수와 비정적(non-static) 지역 변수는 사실 같은 의미를 가지고 있으며, 전역(global) 변수와 정적(static) 변수는 정적 영역 변수에 해당합니다.

 이렇게 변수의 종류가 다양하게 분류되는 것은 프로그래밍 시 여러 가지 상황이 발생하게 되고 이때 이런 상황에 맞는 변수 설정을 위한 여러 가지 개념들이 필요했기 때문이며 이러한 여러 개념들이 서로 복합적인 관계를 맺으며 사용되는 것은 그러한 개념들을 구체화 시키는 과정에서 구현 상의 이유로 만들어진 부수효과(side-effect)라 할 수 있습니다.

가령 포트란 같은 경우 모든 변수가 전역 변수로 지정됩니다(참고로 저는 포트란을 직접 사용해 본적은 없습니다.). , 지역 변수라는 개념이 없습니다. 따라서 파라미터 전달이나 스택 변수 생성에 따른 함수 호출 오버헤드가 없기 때문에 간단한 프로그래밍 시 좋은 성능을 보여줍니다.

그러나 대신 일회성 변수를 사용하더라도 지속적인 변수 유지가 필요하기 때문에 이름 짓기(naming) 문제나 혹은 같은 변수를 다른 용도로 계속 재사용하게 되고 따라서 복잡한 프로그래밍 시 코드가 난해해지는 단점이 있습니다.

그래서 C와 같은 프로그래밍 언어에서는 지역 변수라는 개념을 도입했으며 그에 따라 scope라는 개념이 생겨났습니다.

그리고 이런 scope라는 개념이 들어가면서 동시에 storage duration이라는 개념이 생겨났습니다. 더 이상 참조가 필요 없는, 다시 말하면 scope를 벗어난 지역 변수에 대해서 프로그래머가 일일이 지정하지 않아도 자동적으로 해당 지역 변수의 메모리를 컴파일러가 알아서 해제해줌으로써 보다 추상화된 프로그래밍이 가능하게 되는 것입니다.

그래서 이런 지역 변수의 특징을 구체화하는 과정에서 가장 손쉽고 오버헤드가 적게 드는 기법을 구현한 것이 바로 스택을 통한 지역 변수 관리 기법인 것입니다.(따라서 스택 변수는 곧 비정적 지역 변수가 됩니다.)

어쨌든 점점 프로그래밍이 고난이도 작업이 되고 그에 따라 요구 조건이 까다로워지면서 보다 다양한 개념의 메모리 및 변수 관리가 필요하였고 그에 따라 C/C++와 같은 언어에서는 직접 사용자가 메모리를 관리하는 포인터 및 동적 할당 개념이 생겨났습니다.

그런 와중에 '특정 scope를 가지면서 해당 변수의 storage duration scope에 상관없이 프로그램 실행 시간 내내 유지 될 수 있는' 그런 기법이 필요하게 되었습니다. 전역 변수는 프로그램 실행 시간 내내 유지되지만 대신 다른 블럭이나 모듈, 파일들에서 해당 변수를 참조할 수 있게 되고 그러면 프로그램의 안정성이 떨어질 수 있기 때문에, 가급적 참조 범위를 최소화하는 것을 미덕으로 여기는 프로그래밍 세계에서는 다른 대안을 필요로 한 것입니다.

한편, static 변수는 앞서 말한 바와 같이 컴파일 시점에 바인딩이 되는 변수입니다. 그런데 이 변수를 전역이 아닌 지역 변수로 선언을 한다면 뭔가 비 논리적인 상황이 발생합니다. 왜냐하면 지역 변수는 스택을 통해 관리되므로 그 속성(여러 속성들이 있겠지만 여기서는 메모리 주소값)이 수시로 바뀌게 되는데 이는 static의 원칙에 어긋나는 동작입니다.

결국 static 지역 변수는 허용을 하지 말거나 혹은 지역 변수와는 독립적인 처리가 필요하게 되었습니다. 그리고 C/C++ 에서는 후자를 선택하였습니다. 그 이유는 아마도 앞서 언급한 필요성 때문이 아닐까 생각합니다.(사실 위의 이야기들은 다 제가 추측한 이야기입니다. 실제로 어떤 이유로 static 지역 변수가 생겨났는지는 K&R만이 알겠죠...)

따라서 static으로 선언된 지역 변수는 다른 지역 변수와 달리 스택이 아니라 정적 영역에 할당이 됨으로써 해당 scope를 벗어나 스택이 해제되더라도 그 상태를 유지할 수 있게 되었습니다. 어차피 static 전역 변수 역시 정적 영역에 할당되므로 구현 상으로도 통일된 처리가 가능하여 이는 충분히 합리적인 결정이라 생각됩니다.

어쨌든 결국 static 지역 변수는 지역 변수의 일부 특성(scope)과 전역 변수의 일부 특성(static storage duration)을 갖는 독특한 존재가 됐습니다. 예를 들자면,

 

#include <iostream>

 

int sum(int a)

  {

  static int x = 0; /// 최초 sum 호출 시 한번 만 생성&초기화됨

  x += a;

  return x;         /// sum()함수 종료 시에도 소멸 안됨

  }

 

/* 1부터 100까지 합을 출력하는 프로그램*/

int main()

  {

  int i = 0;

  for (; i < 100; ++i)

    sum(i);

   

  std::cout << sum(100) << 'n';

 

  return 0;       /// main()함수 종료 시 sum()함수 내에 있는 x 변수 소멸

  }

 

이런 식의 사용이 가능합니다. ,

 

#include <iostream>

int* local_fun(int a)

  {

  int x = 0;

  x += a;

  return &x;

 

int* static_fun(int a)

  {

  static int x = 0;

  x += a;

  return &x;

  }

 

int main()

  {

  int* p = NULL;

  p = local_fun(3);

  std::cout << *p << 'n';   /// 비정상적인 값 출력

  p = static_fun(3);

  std::cout << *p << 'n';   /// 정상값(3) 출력

  return 0;

  }

 

이처럼 지역 변수의 경우 스택을 통해 메모리가 관리되므로 외부 참조가 불가능하지만 static 지역 변수의 경우 정적 영역에서 메모리가 관리되므로 포인터 혹은 레퍼런스를 통한 참조가 가능합니다. 여기서 scope에 대한 제약은 어떤 실행 메카니즘에 의한 것이 아니라 단지 컴파일러에 의한 문법 검사에 의한 것임을 알 수 있습니다.(이런 특징 때문에 static 객체는 singleton 패턴을 구현하는데 사용되기도 합니다.)

어쨋든 이렇게 static 객체는 전역 객체로 선언되면 internal linkage 특성을 가지지만 지역 객체로 선언될 경우 static storage duration 특성을 가지게 됩니다. 그리고 이런 특성이 모두 static이라고 하는 용어가 가진 개념에 의해 발생된 것임을 알 수 있습니다.

 

C++에서의 static

C에서 static이 가진 의미는 위에서 말한 두 가지가 전부입니다. 그러나 C++에서는 객체지향 패러다임이 도입되었고 그에 따라 클래스라고 하는 개념이 도입되었습니다.

클래스는 다 아시다시피 '무언가 공통된 책임을 수행하기 위해 같이 모아 놓으면 좋을 만한 변수와 함수들을 하나의 모듈로 캡슐화시킨 사용자 정의 타입'입니다. 그리고 이런 클래스 타입에 의해 생성된 변수들을 '객체(Object)'라고 합니다.

사실 이런 클래스나 객체라는 것은 이름은 그럴 듯 하지만 막상 그 세부 구조를 밑바닥까지 파헤쳐 보면 C에서 사용하는 구조체(structure)와 큰 차이가 없습니다. 멤버 변수들은 단지 구조체 멤버에 사용자 권한이라고 하는 컴파일 타임 제약 사항만을 추가한 것에 불과하며, 멤버 함수라고 하는 것은 실상 해당 클래스 객체의 포인터를 파라미터로 자동 추가해 주는 편이성 높은 함수에 불과합니다. ,

 

class CppClass

  {

  public:

    CppClass() : x_(0) {}

    void SetX(int a) { x_ = a; }

    int x_;

  }

 

위의 클래스를 C언어로 바꾸게 되면

 

struct CStruct

  {

  int x_;

  }

 

void CStruct_ctor(CStruct* this)

  {

  this->x_ = 0;

  }

 

void CStruct_SetX(CStruct* this, int a)

  {

  this->x_ = a;

  }

 

이렇게 됩니다. 그래서 실제 사용시에

 

CStruct tmp;

CStruct_ctor(&tmp);

CStruct_SetX(&tmp, 3);

 

이렇게 사용할 것을 클래스를 이용해서

 

CppClass tmp;

tmp.SetX(3);

 

이런 식으로 사용함으로써 생성자나 SetX()와 같은 멤버 함수가 CppClass라는 클래스의 객체에 밀접하게 관련되었다는 것을 프로그래머가 보기에 보다 직관적이 될 수 있도록 해준 장치에 불과합니다.

물론 이렇게 맥 빠지게 말을 하였지만 C++, JAVA, Smalltalk들과 같은 객체 지향 언어들은 객체 지향 패러다임을 통해 상속, 다형성, 캡슐화라는 다양한 장치를 제공하여 현재 가장 널리 사용되는 프로그래밍 언어인 것은 틀림없습니다.(적어도 이제 사람들이 더 이상 C를 반드시 배우려고 하지는 않습니다.)

어쨋든 객체 지향 언어들은 모든 프로그래밍을 객체 단위로 구현하고 조직화하게 되며 따라서 모든 변수들은 자신이 속한 객체와 그 생명 주기(life time)를 같이 합니다. 다시 말하면 멤버 변수의 scope storage duration은 자신이 속한 객체에 영향을 받는다는 뜻입니다.

그런데 여기서도 지역 변수에서와 유사한 문제가 발생합니다. , 클래스 멤버 변수 선언 시에 static을 앞에 붙히게 되면 어떻게 되느냐 하는 것입니다.

클래스 멤버 변수는 객체가 생성될 때 같이 생성되고 객체가 소멸될 때 같이 소멸됩니다. 게다가 객체가 지역 변수로 선언되면 멤버 역시 스택에 위치하게 되며 객체가 동적 할당되면 멤버 역시 동적 영역에 위치합니다.

그런데 앞서 언급했듯이 static은 지역 변수든 전역 변수든 상관없이 static storage duration을 갖습니다. 따라서 아무리 객체 지향이라 하더라도 이러한 기존의 법칙을 거스르는 행동을 하는 것은 논리적이지 못합니다.

따라서 이것 역시 두 가지 선택이 필요하게 되었습니다. 멤버 변수에 대한 static을 허용하느냐, 그렇지 않느냐...그리고 C++은 전자를 선택하였습니다. 단 이렇게 하고 나니 한 가지 문제가 발생하였습니다. static 멤버 변수를 허용하게 되면 기존의 static 특성을 따르기 위해서 static storage duration을 가져야 하고 그러려면 이 변수는 정적 영역에 위치하여야 하는데 객체는 반드시 정적 영역에 위치하리라는 보장이 없기 때문입니다.

결국 이런 모순을 해결하기 위해서는 static 멤버 변수를 객체에서 분리하여야 합니다. , 객체의 부분 집합으로써가 아니라 단지 클래스의 이름 공간에 한정 받는 전역 변수처럼 취급이 되는 것입니다. 다행스럽게도 이것은 기존의 static 정의에서 크게 벗어나지 않는 개념입니다. ,

 

  1. static 변수는 한 번 생성되면 프로그램 종료 시까지 계속 유지된다.(static storage duration)

  2. static 변수는 scope에 영향을 받는다.

    - 전역 객체는 정의된 파일 scope에 한정되어 참조 가능하며(internal linkage), 지역 객체는 정의된 local scope에 한정되어 참조 가능하다.(no linkage)

 

static 클래스 멤버 변수는 위의 정의에서 1번과 일치하며 2번의 경우 영향 받는 scope를 클래스 이름공간(namespace)라는 것으로 확장하여 생각하면 역시 문제될 것이 없습니다.

결국 static 클래스 멤버 변수는 객체와 상관없이 클래스 범위 연산자(::)를 통해서 참조가 가능한 독특한 특징을 가진 전역 객체가 되었으며 동시에 '객체가 몇 개가 생성되든 오직 하나만 존재함(singleton)'이라는 성질을 가지게 되었습니다.

실제 프로그래밍에서 static 멤버 변수는 어떤 클래스에 관련되지만 특정 객체에 영향을 받지 않는 성질을 가진 데이터를 취급하는데 아주 유용하게 사용됩니다. reference counter가 그 대표적인 예라 할 수 있습니다.

한편 클래스 멤버 함수는 다른 경우가 됩니다. 함수라는 것은 어차피 storage duration이라는 것이 존재하지 않기 때문에 일반 함수의 경우 static, non-static의 구분이 'internal linkage인가 아니면 external linkage인가'로 결정이 됩니다.

여기서 클래스 멤버 함수의 경우는 internal linkage라는 것이 큰 의미가 없습니다. 어차피 private이나 protected와 같은 권한 지정 키워드가 있기 때문에 얼마든지 사용 범위에 제한을 가할 수 있기 때문입니다. 따라서 이것 역시 두 가지 선택 사항이 존재하게 됩니다. 멤버 함수에 대해서 static을 허용하느냐 그렇지 않느냐...C++는 역시 허용하는 쪽에 손을 들어 줍니다.

사실 static이라는 키워드는 기본적으로 모든 선언문 형식에 들어갈 수 있는 storage class specifier라고 하는 지정자의 일종이기 때문에(C에서 그렇게 정의했기 때문에) 자꾸 이런 저런 제약을 가하는 것은 C의 자유로운 문법을 계승한 C++ 입장에서 그다지 바람직하지 않다라고 생각했을 것입니다.

어쨌든 멤버 함수에 static을 허용하였고 그에 따른 어떤 의미 부여가 필요하였습니다.(앞서 말한 대로 internal linkage라는 성질은 큰 의미가 없으므로...)

여기서 C++은 멤버 변수가 객체에 영향을 받지 않는 클래스 이름 공간에 속한 전역 객체로써 취급되었듯이 멤버 함수 역시 동일한 성질을 부여하게 됩니다. , static 멤버 함수는 객체가 없어도 호출이 가능하며 단지 클래스 이름 공간에 한정을 받는 함수가 된 것입니다. 따라서 static 멤버 함수는 다른 멤버 함수처럼 this 포인터를 암시적으로 받는 특성이 없이 일반 함수와 똑같은 호출 구조를 가지게 되었습니다. 결국

 

class StaticClass

  {

  public:

    static int x_;

    static int func() {};

  }

 

이것은

 

namespace gimmesilver

  {

  extern int x;

  int func() {};

  }

 

이것과 거의 동일한 특성을 가집니다. 따라서 non-static 멤버 함수들은 암묵적으로 넘겨받은 this 포인터를 통해서 해당 객체의 멤버 변수나 다른 non-static 멤버 함수를 참조/호출할 수 있지만 static 멤버 함수들은 그러한 참조할 만한 객체 포인터가 없으므로 객체의 멤버 변수나 non-static 멤버 함수를 참조/호출할 수 없는 것입니다.(이것 역시 초보자들이 흔히 하는 실수입니다.)

 

Post face

정리 들어갑니다...

 

static 변수

  1. static storage duration을 갖는다.

  2. scope의 영향을 받는다.

           - 전역 변수 : file scope의 영향을 받으며 internal linkage라고 한다.

           - 지역 변수 : block scope의 영향을 받는다.

           - 클래스 멤버 변수 : 클래스 이름공간의 영향을 받는 전역 객체이다.

   

static 함수

  1. 일반 함수 : internal linkage를 갖는다. , 외부 파일에서 해당 함수를 호출하지 못한다.

2. 클래스 멤버 함수 : this 포인터를 갖지 않는다. 따라서 객체의 멤버 변수나 멤버 함수를 직접 참조/호출할 수 없다. , 같은 클래스의 static 멤버 변수나 static 멤버 함수는 직접 참조/호출이 가능하다.

 

'Development > 기타' 카테고리의 다른 글

Mobile XE 설정하기  (0) 2010.08.24
[GPS]GPS모듈 구현중  (0) 2009.12.14
Mozilla XPCOM document  (0) 2009.10.25
[펌]회사에서 네이트 온 하기!!  (0) 2008.06.13
Creating Application with mozila  (0) 2008.05.13
Posted by 까 치
,
Link from  http://www.midi.org/techspecs/dls/dlsoverview.php


DLS - DownLoadable Sounds DLS Specifications
DLS Certification Program
DLS Proprietary Chunk IDs
DLS Products and Tools
Downloadable Sounds - Level 1 Overview (1997)

(Excerpted from an Article written for Yamaha's XG-Extra Newsletter, by Howard Massey)

The 1997 Winter NAMM show provided a venue for what may prove to be one of the most significant events in the history of MIDI: the MMA's unanimous ratification of Downloadable Sounds (DLS) Level 1. In this article, we'll preview the main features of this innovative extension to MIDI.

Unashamedly targeted for CD-ROM and Internet entertainment applications, DLS provides a means for game developers and composers to add their own custom sounds to the GM sound set stored in a sound card's ROM. DLS-compatible devices will automatically download these custom sounds from card, disk or CD-ROM into system RAM, allowing MIDI music to be freely augmented with new instrument sounds, dialog or special effects - thus providing a universal interactive playback experience, along with an unlimited palette of sounds. At the same time, it enables the wavetable synthesizers in computer sound cards to deliver improved audio at no additional cost. The DLS specification will be publicly available this spring, and DLS-compatible sound cards and chip sets are expected to begin shipping later this year.

DLS Software

The DLS file format generally follows standard Microsoft RIFF layout (with a form type of "DLS"), utilizing chunks and sub-chunks and incorporating standard WAVE files. The following description is taken from a preliminary version of the DLS Specification:

"The DLS file format is used to store both the digital sound data and articulation parameters needed to create one or more 'instruments.' An instrument contains 'regions' which point to WAVE 'files' (samples) also embedded in the DLS file. Each region specifies a MIDI note and velocity range which will trigger the corresponding sound and also contains articulation information such as envelopes and loop points. Articulation information can be specified for each individual region or for the entire instrument."

The flexible file structure utilized by DLS means that a single sample may serve as different regions within different instruments-for example (as shown in Figure 1), a given sample can serve as region "1b" for instrument "1" while at the same time acting as region "2a" for instrument "2."


(Figure 1)

There are two basic kinds of DLS Level 1 instruments: Melodic instruments and drum kits. As shown in Figure 2, melodic instruments can be accessed by any MIDI channel except channel 10 and contain up to 16 regions, with all regions utilizing a single set of articulation data.


(Figure 2)

In contrast, there is a single DLS Level 1 drum kit, which can only be accessed via MIDI channel 10. As shown in Figure 3, it contains up to 128 regions, one for each drum note (which can be mapped to a single key or to a contiguous range of keys). Each region has its own set of articulation data and points to a single sample.


(Figure 3)

In both melodic instruments and drum kits, multiple regions can point to the same sample if required.

As shown in Figure 4, the audio engine driving the DLS Level 1 instrument is relatively simple, consisting of a single digital oscillator routing signal into a digitally controlled amplifier (DCA).


(Figure 4)

 

The digital oscillator is fed a 16- or 8-bit sample and plays it back while modulating its pitch according to the incoming pitch control articulation data. The sample itself contains data that defines its sample rate (minimum rate is 22.05 kHz), base MIDI note number, and, if looped, loop start and end point. The DCA modulates the volume of the resulting signal according to the incoming volume control articulation data. DLS Level 1 does not allow for layering of oscillators within a single instrument; however, devices that support both DLS and GM may optionally use a voice prioritization scheme that "steals" the second layer of a GM instrument and assigns it instead to a DLS melodic instrument (however, within the standard 16 MIDI channels, only one drum kit can be used, and, if previously downloaded and selected, a DLS drum kit takes precedence over the GM drum kit).

DLS Level 1 articulation data includes an LFO, two discrete ADSR envelope generators, and several MIDI controller inputs. One of the envelopes routes signal to the digital oscillator and is used to vary pitch over time, while the other routes signal to the DCA and is used to vary volume over time. In addition to standard attack time, decay time, sustain level, and release time control parameters, there are additional controls that enable EG Velocity to Attack Scaling (where the MIDI note number affects the attack time) and Decay Scaling (where the MIDI note number affects the decay time). The EG polarity can also be set to positive or negative (around zero).

The LFO may be used for either pitch or volume control. It utilizes a sine wave (a triangle wave may be optionally substituted) and contains controls that allow its frequency to be set (between 0.1 Hz and 10 Hz) and start time to be delayed (between 10 ms and 10 seconds).

MIDI controller inputs include: key number, key velocity, pitch bend, the continuous controller messages Volume (cc #7), Expression (cc #11), Pan (cc #10), Modulation Wheel (cc #1), Sustain Pedal (cc #64) and RPNs 0, 1, and 2 (Pitch Bend, Fine Tuning, and Coarse Tuning), as set by the Data Entry MSB/LSB (cc #6 and #38). In addition, the Reset All Controllers and All Notes Off channel mode messages are supported. Controller power-on default values for DLS Level 1 devices are as follows:

  • Volume = 100
  • Expression = 127
  • Pan = 64
  • Modulation Wheel = 0
  • Pitch Bend = 0
  • Sustain Pedal = 0

DLS-compatible devices will enter a special "DLS" mode upon receipt of the following DLS System On system exclusive message:

F0h 7Eh <device ID> 0Ah 01h F7h

Once in DLS mode, the device will be able to place a downloaded sound into any specified bank/instrument location, thus avoiding conflict with other operating modes (such as GM, GS, or XG) which require certain voices to be loaded into specific locations. DLS Level 1 devices must support MIDI Program Change messages (and, optionally Bank Select messages) as the means of selecting the instrument to be played by each channel. However, they only have to provide a minimum of a single melodic instrument bank for storage of downloaded sounds. In such cases, the designer of the device driver has the option of "virtualizing" incoming Bank Select MSB/LSB messages (cc #0 and #32) so that they point to the device's actual available address space.

The following DLS System Off system exclusive message is used to revert DLS-compatible devices will back to their default operating mode:

F0h 7Eh <device ID> 0Ah 02h F7h

As with the GM System On and GM System Off messages, a <device ID> = 7Fh in the DLS System On and DLS System Off messages will act as a "broadcast" message to all compatible devices.

DLS Hardware

The minimum hardware requirements for DLS-compatible devices are as follows:

  • As with GM, a minimum of 24 simultaneous voices must be supported.
  • The device must have enough RAM to store at least 256K words of sample data (512KB organized as 16-bit samples).
  • The device must be capable of simultaneously handling a minimum of 128 DLS instruments, 128 sets of articulation data, and 128 regions or samples.
  • Minimum output sample rate is 22.05 kHz.
  • Devices that support both DLS and GM must be able to support both simultaneously, sharing MIDI channels between the two modes.

The MMA will shortly be implementing a certification process for DLS-compatible devices, utilizing an independent testing laboratory.

Not willing to rest on their laurels, the MMA (and their affiliate IASIG, the Interactive Audio Working Group) is also moving ever-forward. DLS Level 2 discussions are due to start shortly. Features being considered for Level 2 include: multiple articulations; the addition of filters and effects such as reverb; support for higher sample rates, alternate tunings, and different loop types (possibly including the use of data compression); improved (and dynamically selectable) voice prioritization; enhanced envelope controls and LFO wave shapes; and extended minimum requirements for memory and polyphony. Also, Level 2 may remove the distinction between melodic instruments and drum kits altogether, enabling all instruments to support 128 regions.

Special thanks to Tom White of the MMA, to Monty Schmidt of Sonic Foundry, and to Dave Sparks of Sequoia for their assistance in preparing this article. The illustrations in this article are Copyright 1997 MMA. This article appears courtesy of Yamaha Strategic Business Alliance (http://www.ysba.com)

Posted by 까 치
,

sigaction

Development/Linux 2009. 8. 1. 15:35

1장. sigaction(2)

차례
1.1. 사용법
1.2. 설명
1.3. 반환값
1.4. 에러
1.5. 예제
1.6. 참고문헌

시그널 처리관련 함수


1.1. 사용법

#include <signal.h>
int sigaction(int signum,  const  struct  sigaction  *act,
	struct sigaction *oldact);
int  sigprocmask(int  how,  const  sigset_t *set, sigset_t *oldset);
int sigpending(sigset_t *set);
int sigsuspend(const sigset_t *mask);
		


1.2. 설명

sigaction() 시스템 호출은 특정 시그널의 수신에 대해서 취할 액션을 설정하거나 변경하기 위해서 사용된다.

signum는 시그널을 명시한다. SIGKILLSIGSTOP를 제외한 모든 시그널이 타당한 시그널이 될 수 있다.

만약 actnull이 아니라면 signum번호를 가지는 시그널에 대해서 act함수가 설치된다. 만약 oldact가 null이 아니라면 이전의 액션은 oldact에 저장된다.

sigaction구조체는 다음과 같이 정의되어 있다.

struct sigaction 
{
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
}
		

sa_handler

signum번호를 가지는 시그널이 발생했을 때 실행된 함수를 설치한다. 함수외에도 SIG_DFL과 SIG_IGN을 지정할 수 있다. 전자는 시그널에 대한 기본행동을 후자는 시그널을 무시하기 위해서 사용한다.

sa_mask

sa_handler에 등록된 시그널 핸들러 함수가 실행되는 동안 블럭되어야 하는 시그널의 마스크를 제공한다. SA_NOMASK가 적용되어 있지 않다면

sa_flags

시그널 처리 프로세스의 행위를 수정하는 일련의 플래그들을 명시한다. 다음중 하나 이상의 것들에 의해서 만들어 진다.

SA_NOCLDSTOP

만약 signum이 SIGCHLD라면, 자식 프로세스가 SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU등을 받아서 중단되었을 때 이를 통지 받을 수 없게 된다.

SA_ONESHOT, SA_RESETHAND

일단 시그널 처리기가 호출되면, 기본 상태에 대한 시그널 액션을 재 저장한다. 이는 signal(2)호출에 대한 기본 행위이다.

SA_RESTART

일부 시스템 호출들이 시그널을 통해 재시작할 수 있도록 함으로서 BSD 시그널과 호환되도록 한다.

SA_NOMASK, SA_NODEFER

시그널이 자체 시그널 처리기로부터 수신 받지 않도록 한다.

SA_SIGINFO

시그널 처리기가 하나가 아닌 3개의 인자를 취할경우 sa_handler대신 sigaction의 siginfo_t를 이용할 수 있다. siginto_t는 다음과 같이 정의된 구조체이다.

siginfo_t {
    int      si_signo;  /* 시그널 넘버 */
    int      si_errno;  /* errno 값 */
    int      si_code;   /* 시그널 코드 */
    pid_t    si_pid;    /* 프로세스 ID 보내기 */
    uid_t    si_uid;    /* 프로세스를 전송하는 실제 사용자 ID */
    int      si_status; /* Exit 값 또는 시그널 */
    clock_t  si_utime;  /* 소모된 사용자 시간 */
    clock_t  si_stime;  /* 소모된 시스템 시간 */
    sigval_t si_value;  /* 시그널 값 */
    int      si_int;    /* POSIX.1b 시그널 */
    void *   si_ptr;    /* POSIX.1b 시그널 */
    void *   si_addr;   /* 실패를 초래한 메모리 위치 */
    int      si_band;   /* 밴드 이벤트 */
    int      si_fd;     /* 파일 기술자 */
}
						
SIGCHLD 시그널은 si_pid, si_uid, si_status, si_utime, si_stime를 채운다. si_int, si_ptr은 시그널의 송신자에 의해서 명시될 수 있다.

si_code는 왜 시그널이 보내졌는지를 지시한다.

sigprocmask()는 현재 블록된 시그널들을 변경시키기 위해서 사용한다. 호출의 행위는 how 값들에 대해서 의존적이 된다. how는 다음중 하나를 선택할 수 있다.

SIG_BLOCK

set에 설정된 시그널을 블럭 시그널셋에 추가시킨다.

SIG_UNBLOCK

시그널 셋set의 시그널을 현재의 블럭된 시그널에서 삭제한다.

SIG_SETMASK

시그널 셋set의 시그널을 블럭화된 시그널로 지정한다.

oldset이 null이 아니면, 시그널 마스크의 이전 값은 oldset에 저장된다.

sigpending()함수는 전달 시그널들에 대한 검사를 허용한다. 전달 시그널들의 마스크는 set에 저장된다.

sigsuspend()는 프로세스의 시그널 마스크를 일시적으로 mask로 대체하고, 시그널이 수신될때까지 프로세스를 중지시킨다.


1.3. 반환값

성공하면 0을 실패하면 -1을 리턴한다.


1.4. 에러

EINVAL

부적절한 시그널이 지정되거나. 무시할 수 없는 SIGKILL SIGSTOP에 대한 액션을 변경하고자 할 경우

EFAULT

act, oldact, set, oldset 이 타당하지 않은 메모리 영역을 가리킬 경우

EINTR

시스템 호출이 인터럽트 되었다.


1.5. 예제

#include <signal.h> 
#include <unistd.h> 
#include <string.h> 
#include <stdio.h> 

void sig_int(int signo);
void sig_usr(int signo);

int main()
{
    int i = 0;
    struct sigaction intsig, usrsig;

    usrsig.sa_handler = sig_usr;
    sigemptyset(&usrsig.sa_mask);
    usrsig.sa_flags = 0;

    intsig.sa_handler = sig_int;
    sigemptyset(&intsig.sa_mask);
    intsig.sa_flags = 0;

	// SIGINT에 대해서 sig_int를 등록한다. 
    if (sigaction(SIGINT, &intsig, 0) == -1)
    {
        printf ("signal(SIGINT) error");
        return -1;
    }    

	// SIGUSR2에 대해서 usrsig를 등록한다. 
    if (sigaction(SIGUSR2, &usrsig, 0) == -1)
    {
        printf ("signal(SIGUSR2) error");
        return -1;
    }    

    while(1)
    {
        printf("%dn", i);
        i++;
        sleep(1);
    }
}

void sig_int(int signo)
{
    sigset_t sigset, oldset;

	// 핸들러가 수행되는 동안 수신되는 모든 시그널에 대해서
	// 블럭한다.  
    sigfillset(&sigset);
    if (sigprocmask(SIG_BLOCK, &sigset, &oldset) < 0)
    {
        printf("sigprocmask %d error n", signo);
    }
    fprintf(stderr, "SIGINT !!!!n");
    sleep(5);
}

void sig_usr(int signo)
{
    printf("sig_usr2n");
}
		

Posted by 까 치
,

Linux daemon

Development/Linux 2009. 7. 6. 21:19
아래 글은 Nick lib 의 푸우님께서 작성해주신 글입니다.
문제시 삭제 조치하겠습니다.
Ref  http://www.nicklib.com/bbs/board.php?bo_table=bbs_column&wr_id=9


요즘 통 글을 안 올렸네요.
요즘 회사일로 정신이 없네요.
죄송하구요. ^^; 
아무튼 이번에는 UNIX/LINUX의 Deamon 프로세스 만드는 것에 대해 정리해 보도록 하겠습니다.
 
1. Daemon이란?
 
뭐 이글을 관심이 있어서 읽고 계시는 분 중에 Daemon이 뭔지를 모르시는 분은 없겠지만...
그래도 한번 정리해 봅니다.
Daemon의 뜻 자체는 "악마", "악령", "귀신"과 같이 좀 무시무시 합니다.
하지만 Daemon의 어원은 신화에서 신과 인간사이의 초자연적인 영적존재로서의 수호신에서 나왔다고 합니다. 뭐 좋은쪽으로 생각하죠.
컴퓨터 상에서 Daemon은 겉으로는 보이지 않지만 항상 뒤에서 뭔가를 계속해서 수행해 주는 프로그램을 말합니다. 예로 httpd, ftpd, telnetd 등...
이런면에서 왜 Daemon이라는 이름 지었는지 이해가 가네요.
윈도우즈에서는 Daemon이라는 용어가 마음에 들지 않았는지 아니면 뭔가 꼭 튀게 해야한다고 생각했는지 몰라도... UNIX/LINUX의 Daemon과 같은 역할을 하는 프로그램을 Service 라고 합니다.
"데몬"은 이름에 뭔가 철학을 담아 지은것 같은데... "서비스"... 참 단순해서 좋긴 좋네요. ㅋㅋ  ^^;
 
Daemon 프로그램이 되려면 몇가지 특징을 갖추어야 하지만 가장 중요한 것은 "OS가 부팅되면 사용자의 개입없이 실행될 수 있을 것"입니다.
이 말은 다시 말해 누군가가 로그인을 하여 굳이 실행하지 않아도 OS의 init프로세스에 의해 실행될 수 있어야 한다는 이야기 이고 이럴려면 프로그램을 제어하는 터미널(tty)를 갖지 말아야 한다는 것 입니다. 반대로 말하면 누군가 로그인 해서 데몬을 시작시켰다면 로그아웃할 때 해당 데몬은 계속해서 살아 있어야 한다는 말도 됩니다.
 
이제 이러한 특징을 갖는 프로그램을 만드는 방법을 단계별로 알아보도록 하죠.
 
 
2. 로그 아웃시 프로그램 종료되지 않게 하기
 
우선 데몬은 아니더라도 데몬과 비슷하게 수행하면 계속해서 작업을 수행 할 수 있도록 하는 방법에 대해서 알아 보겠습니다.
 
기본 소스는 다음과 같습니다.
 
#include <stdio.h>

int work()
{
        FILE *fp;
        unsigned long count=0;

        fp=fopen("/tmp/daemon.out""wt");
        if(fp==NULL) {
                printf("File Open Errorn");
                return -1;
        }

        while(1){
                sleep(1);
                fprintf(fp, "%lun", count);
                fflush(fp);
                fprintf(stdout, "%lun", count++);
        }
        fclose(fp);

        return 0;
}

int main(int argc, char *argv[])
{
        work();

        return 0;
}
 
 
위의 프로그램은 간단하니깐 보시면 아시겠지만 main()함수가 수행되면 work()라는 함수를 수행하게 됩니다.
work()함수는 무한루프를 돌면서 1초마다 "/tmp/daemon.out"파일에 카운팅을 기록합니다.
또 표준출력으로도 같은 값을 출력하는 프로그램입니다.
위의 소스를 컴파일하여 daemon이라는 실행파일을 생성했다고 하고 다음의 글들을 작성토록 하겠습니다.
 
자 그럼 로그아웃이 될때 어떤 현상이 일어날지에 대해 한번 알아보도록 하죠.
 
로그아웃하거나 해당 터미널이 끊기게 되면 OS 해당 터미널을 제어단말로 사용하던 모든 프로그램에게 특별한 신호를 보내게 됩니다.
이 신호가 SIGHUP, 즉, Hangup 시그널입니다.
일반적인 프로그램은 이 Hangup 시그널을 받으면 정상종료를 하게 됩니다.
뭐 프로세스가 알아서 정상종료 한다기 보다는 이 시그널을 어떻게 처리하라는 루틴을 구현하지 않은 프로세스는 조용히 OS가 죽이는 것이죠.
 
OS에 SIGHUP을 무시하게 해주는 'nohup'이라는 쉘명령어가 있습니다.
일반적인 사용방법은 다음과 같습니다.
 
#nohup daemon&
 
위의 명령어 중 daemon은 데몬 프로그램의 이름이고 마지막 &는 이 글의 주제와는 상관없지만 백그라운드로 실행해서 프롬프트를 다시 얻기 위함입니다.
이렇게 daemon을 실행한 뒤 로그아웃하거나 단말기를 끊고 다시 접속한 다음 "ps -ef | grep daemon" 명령을 통해 보시면 daemon이 살아 있음을 보실 수 있으실 것 입니다.
또한 "tail -f /tmp/daemon.out" 해서 보시면 살아 있을 뿐 더러 원래의 기능도 정상적으로 수행 하고 있음을 알 수 있습니다.
 
그런데 여기서 원래 daemon은 파일 뿐만 아니라 화면에도 카운팅 번호를 출력하도록 되어 있었는데 위의 명령의 수행 후에는 아래와 같은 메시지만 남기고 나타나지 않습니다.
 
appending output to `nohup.out'
 
이는 nohup 명령어가 daemon의 표준 출력을 현재 경로의 'nohup.out'이라는 파일로 리다이렉트 시켰기 때문입니다.
 
어찌되었건 쉘에서 제공하는 명령어를 통해 데몬과 유사한 기능을 하도록 해 보았네요. 
 
자 이번에는 'nohup'과 같은 기능을 시스템 함수로 구현해 보도록 하겠습니다.
 
골자는 프로그램 내에서 SIGHUP시그널을 무시할 수 있도록 코딩하는 것입니다.
 
 위 함수에 #include <signal.h> 를 포함하고 main함수를 다음과 같이 고칩니다.
 
 
int main(int argc, char *argv[])
{
        signal(SIGHUP, SIG_IGN);

        work();

        return 0;
}
 
 
지금 부터는 work()함수의 표준 출력이 귀찮아 지므로 표준 출력으로 내보는 문장은 적절히 삭제하셔도 좋습니다.  
위의 main()함수는 signal()함수를 통해 SIGHUP시그널을 무시하도록 설정 한 뒤 work()함수를 수행한 것입니다.
 
컴파일 후 테스트는 여러분의 몫으로 하구요.
 
SIGHUP을 무시하는 방법은 아니지만 또 다른 방법이 있는데 이는 프로세스 내에서 자신의 그룹아이디를 바꾸는 것입니다.
 
이는 로그 아웃시 현재 터미널을 제어 단말기로 하고 있는 프로세스에 OS가 SIGHUP시그널을 보낼 때 쉘프로세스의 아이디를 그룹아이디로 갖는 모든 프로세스에게 보낸다는 것에 착안한 것입니다.
 
즉, 프로그램은 특정 쉘프로세스에서 수행되기 때문에 일반적으로 프로세스 그룹아이디는 수행 환경이 되었던 쉘프로세스 아이디를 갖게 되는데 이를 프로세스 내에게 바꾸는 것입니다. 
 
int main(int argc, char *argv[])
{
        int newpgid;

        newpgid = setpgrp();

        work();

        return 0;
}
 
위의 소스에 보이는 setpgrp()함수는 자신의 프로세스 그룹을 자신의 프로세스아이디로 바꿔줍니다.
이렇게 하면 로그아웃시 이 프로세스는 아예 SIGHUP시그널을 받지도 않으므로 종료되지 않습니다.
 
자 여기까지는 데몬 프로그램을 만드는 법이라기 보다는 일반적으로 프로세스에서 일어나는 일과 이들을 처리하는 방법, 그리고 간단히 데몬과 유사한 효과 내기라고 생각하시면 될 듯 싶습니다.
 
 
3. fork에 의한 Daemon만들기
 
 
위에서 이야기 한 방법들도 뭐 잘못된 방법들은 아니지만 일반적으로는 이제부터 이야기 하는 방법으로 주로 데몬을 만듭니다.
main()함수를 다음과 같이 바꾸세요.
 
int main(int argc, char *argv[])
{
        int pid;

        pid=fork();
        switch(pid){
        case -1:
                fprintf(stderr, "Fork Errorn");
                break;
        case 0:         // child
                break;
        default:        // parent
                return 0;
        }

        work();

        return 0;
}
 
위의 main()함수는 프로그램이 시작되자 마자 fork()를 수행해서 자식프로세스는 work()함수를 수행하고 부모프로세스는 종료하게 합니다.
그러면 사실 자식프로세스는 아직 살아 있는데 부모프로세스가 터미널과 연결되어 있었는데 사라지게 됩니다. 고로 자식 프로세스는 제어단말기를 가지지 않게 되고 부모가 없으므로 고아프로세스가 되었네요.
이렇게 고아프로세스가 발생하면 OS는 이 고아 프로세스를 init프로세스의 자식으로 만들어 줍니다.
부모,자식,고아를 이야기 했으니 입양이라고 해야 할까요? ㅋㅋㅋ
하지만 원래 init프로세스는 1번 프로세스로서 조상 프로세스에 해당됨으로 입양이라고 하기는 좀 그렇네요.
 
아무튼 위의 프로그램은 아무 문제가 없습니다. 좀비가 발생하지도 않구요.
 
하지만 이런 케이스를 생각해 보죠.
daemon도 프로세스이므로 현재 작업디렉토리를 갖습니다.
그래서 이 daemon이 실행된 후 daemon을 실행 시켰던 디렉토리가 속한 파일시스템을 "umount" 시키려 한다면 OS는 해당 파일시스템이 사용되고 있다고 umount 하지 못한다고 할 것 입니다.
이런 경우 해당 데몬을 내려야 하는데...
더욱 나쁜 경우는 어떤 데몬이 이 파일시스템을 사용하고 있는지를 알기가 힘들다는 것이죠.
그래서 데몬은 데몬이 되기 전에 현재 경로를 "/'와 같이 "umount"될 수 없는 곳으로 옮기는 것이 좋습니다.
 
또 한가지 고려해야 할 것은 현재 daemon은 뭐 파일에 숫자만 적고 있지만 여러분이 만드는 deamon은 어떤 일을 하게 될 지 사실 모릅니다.
예를 들어 daemon이 또 fork를 해서 자식 프로세스들을 갖을 수도 있고 exec()함수나 system()함수에 의해 아예 다른 프로그램을 실행 시킬 수도 있을 것입니다.
이때 새로 생성되는 프로세스를 현재 daemon이 제어 할 수 없다면 이 또한 문제일 수 있겠죠?
그래서 데몬프로세스는 데몬이 된 직 후 자신이 속한 프로세스 그룹의 리더가 아니라면 새로운 프로세스 세션을 만들고 자신이 리더가 된 후 다음 작업들을 하게 됩니다.  이러한 기능을 하는 함수가 바로 setsid()라는 함수 입니다. (참조: http://teamblog.joinc.co.kr/yundream/226)
 아래 소스는 chdir()과 setsid()함수를 호출하여 위에서 이야기한 내용을 반영하고 있습니다.
 
int main(int argc, char *argv[])
{
        int pid;

        pid=fork();
        switch(pid){
        case -1:
                fprintf(stderr, "Fork Errorn");
                break;
        case 0:         // child
                break;
        default:        // parent
                return 0;
        }

        chdir("/");
        setsid();

        work();

        return 0;
}
 
 
자 여기까지 하면 뭐 데몬이 잘 만들어 진 것 입니다.
 
추가로 실제 데몬을 프로그래밍 하실 때 주의하실 점은 시그널에 대한 처리를 꼭 하시라는 것 입니다.
위의 샘플 소스는 뭐 특별하게 시그널 처리를 할께 딱히 없지만 예를 들어 웹서버와 같이 통신 프로그램인 경우 SIG_PIPE와 같은 시그널을 처리해야 합니다.
 
 
4. 오뚜기 같은 Daemon만들기
 
마지막으로 이야기 하는 것은 일반적인 데몬에 대한 이야기는 아니구요 팁같은 거라고 할까요?
데몬은 서비스 제공하는 프로세스이다 보니깐...
시스템이 시작되면 시작되서 끝날때 까지 살아 있어야 하는데...
우리가 짜는 프로그램이 문제가 없다고 장담할 수는 없겠죠? (저만 그런가?)
 
만약 데몬으로 띄운 프로그램이 죽으면 타격이 크죠.
뭐 그 타격은 타격이더라도 세션이나 트랜잭션이 중요한 서비스가 아닌 경우라면 빨리 다시 시작이라도 시켜서 정상화를 시키고 싶은 경우가 있습니다.
 
이런 경우에 다음과 같은 방법으로 할 수 있을 것입니다.
먼저 일반 데몬을 하나 만들기 위해 일반 fork하시구요.
그리고 다시 fork를 해서 자식프로세스는 서비스를 수행하구요 부모프로세스는 자식이 종료되기를 기다립니다.
그런데 자식프로세스는 무한루프일 거니깐...
부모프로세스 입장에서 자식이 종료되었다면 실제로는 뭔가 문제가 생긴 것이겠죠?
이때 다시 fork를 해서 위의 과정을 반복합니다.
 
소스는 다음과 같습니다.  
 
int main(int argc, char *argv[])
{
        int pid;
        int ret;

        if (( pid = fork()) < 0) {
                fprintf(stderr, "Main Fork Errorn");
                return 0;
        } else if(pid > 0) {
                return 0;
        }

        chdir("/");
        setsid();

        while(1) {
                if (( pid = fork()) < 0) {
                        fprintf(stderr, "Sub Fork Errorn");
                        return 0;
                } else if(pid == 0) {
                        break;
                } else if(pid > 0) {
                        wait(&ret);
                }
        }

        work();

        return 0;
}
 
 
만약 자식이 종료될때 상태를 알고자 한다면 wait()함수의 인자인 ret값을 조사하면 됩니다.
 
자 이렇게 해서 UNIX/LINUX에서 Daemon만들기에 대한 이야기를 마치도록 하겠습니다. 
Posted by 까 치
,
1) Download the latest Nvidia driver for Linux.

2) Install the driver according to the instructions given on the Nvidia web site. You will also have to stop the X-server when installing the driver, a good way would be to install the driver in Recovery Mode.

3) After installation, configure the x-server using:-
Code:
sudo dpkg-reconfigure xserver-xorg


Code:
sudo dpkg-reconfigure -phigh xserver-xorg
, then restart my computer, X starts up perfectly and uses the nv driver. Now if all I do is modify the file /etc/X11/xorg.conf so that the line
Code:
Driver    "nv"
reads
Code:
Driver    "nvidia"


Ok I found some more information that should be useful. There are 3 versions of the nvidia driver on Ubuntu. Each version is in a different package:
  • nvidia-glx-legacy contains version 1.0.7185
  • nvidia-glx contains version 1.0.9639
  • nvidia-glx-new contains version 100.14.19
Each version supports a different set of nVIDIA graphics cards. Go to http://www.nvidia.com/object/IO_32667.html to determine which version supports your graphics card. If you don't know what graphics card you have, go to System > Preferences > Hardware Information, or run the command lspci on the command line.

I have an nVIDIA GeForce FX 5200, which needs nvidia-glx-new, but I had nvidia-glx installed. I'm going to try installing nvidia-glx-new and see whether that gets me any further.


'Development > Linux' 카테고리의 다른 글

sigaction  (0) 2009.08.01
Linux daemon  (0) 2009.07.06
There was an error starting the GNOME Settings Daemon  (0) 2009.03.24
shmctl  (0) 2009.02.20
shmat  (0) 2009.02.20
Posted by 까 치
,
There was an error starting the GNOME Settings Daemon.
Some things, such as themes, sounds, or background settings may not work correctly.
The last error message was:
Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
GNOME will still try to restart the Settings Daemon next time you log in.

덕분에 1시간가량 삽질을 한듯 ...
이유야 여러가지가 있겠지만, 아래와 같이 해결!!

login to a failsafe terminal
$ vi /etc/network/interfaces

Make sure the following lines are in there, and that they arent commented out:

auto lo
iface lo inet loopback

Also, comment out anything involving IPv6 if you dont use it.

then:

$ sudo ifconfig lo up
$ exit

Reboot your box and your good to go. Dont forget to change your session back to Gnome!

참고 : 해당 파일에서 ip setting을 정상적으로 해주면서 해결!!!

Posted by 까 치
,

shmctl

Development/Linux 2009. 2. 20. 16:51

shmctl() 함수는 공유 메모리에 대한 정보를 구하거나 변경 또는 제거합니다. 공유 메모리에 대한 자세한 내용은 shmget() 함수의 내용을 참고하여 주십시오.

헤더

#include <sys/ipc.h>
#include <sys/shm.h>

형태 int shmctl(int shmid, int cmd, struct shmid_ds *buf);
인수
int shmid 공유 메모리 식별 번호
int cmd 제어 명령
struct shmid_ds *buf 공유 메모리 정보 구하기 위한 버퍼 포인터
struct shmid_ds {
  struct    ipc_perm shm_perm;/* 접근권한 */
  int  shm_segsz;            /* 세그먼트의 크기(bytes) */
  time_t    shm_atime;       /* 마지막 접근 시간 */
  time_t    shm_dtime;       /* 마지막 제거 시간 */
  time_t    shm_ctime;       /* 마지막 변경 시간 */
  unsigned short shm_cpid; /* 생성자의 프로세스의 프로세스 id */
  unsigned short shm_lpid; /* 마지막으로 작동한 프로세스의 프로세스 pid */
  short     shm_nattch;     /* 현재 접근한 프로세스의 수 */
  /* 다음은 개별적이다 */
  unsigned short   shm_npages; /* 세그먼트의 크기(pages) */
  unsigned long   *shm_pages;
  struct shm_desc *attaches;    /* 접근을 위한 기술자들 */
  };

shm_perm 멤버의 필드들은 아래와 같이 설정할 수 있습니다.
struct ipc_perm{
       key_t  key;
       ushort uid;   /* owner의 euid 와 egid */
       ushort gid;
       ushort cuid;  /* 생성자의 euid 와 egid */
       ushort cgid;
       ushort mode;  /* 접근 모드의 하위 9 bits */
       ushort seq;   /* 연속 수(sequence number) */
     };
반환
-1 실패
0 성공

예제

예제를 위해 두 개의 프로세스를 만들겠습니다. counter.c 는 공유 메모리에 1초 마다  0부터 계속 증가하는 카운터 문자열을 공유 메모리에 넣으면 show_counter.c에서 공유 메모리를 화면에 출력하도록 하겠습니다.

#include <stdio.h>      // printf()
#include <unistd.h>     // sleep()
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>

#define  KEY_NUM     9527
#define  MEM_SIZE    1024

int main( void)
{
   int    shm_id;
   void  *shm_addr;
   struct shmid_ds   shm_info;

   if ( -1 == ( shm_id = shmget( (key_t)KEY_NUM, MEM_SIZE, IPC_CREAT¦0666)))
   {
      printf( "공유 메모리 생성 실패n");
      return -1;
   }
   else
   {
      printf( "공유 메모리 생성 성공n");
   }

   if ( ( void *)-1 == ( shm_addr = shmat( shm_id, ( void *)0, 0)))
   {
      printf( "공유 메모리 첨부 실패n");
      return -1;
   }
   else
   {
      printf( "공유 메모리 첨부 성공n");
   }

   if ( -1 == shmctl( shm_id, IPC_STAT, &shm_info))
   {
      printf( "공유 메모리 정보 구하기에 실패했습니다.n");
      return -1;
   }

   printf( "공유 메모리를 사용하는 프로세스의 개수 : %dn", shm_info.shm_nattch);

   if ( -1 == shmdt( shm_addr))
   {
      printf( "공유 메모리 분리 실패n");
      return -1;
   }
   else
   {
      printf( "공유 메모리 분리 성공n");
   }

   if ( -1 == shmctl( shm_id, IPC_RMID, 0))
   {
      printf( "공유 메모리 제거 실패n");
      return -1;
   }
   else
   {
      printf( "공유 메모리 제거 성공n");
   }

   return 0;
}
]$ gcc main.c
]$ ./a.out
공유 메모리 생성 성공 공유 메모리 첨부 성공 공유 메모리를 사용하는 프로세스의 개수 : 1 공유 메모리 분리 성공 공유 메모리 제거 성공 ]$
Posted by 까 치
,

shmat

Development/Linux 2009. 2. 20. 16:42

shmat() 함수는 공유 메모리를 마치 프로세스의 몸 안으로 첨부합니다.

공유 메모리는 단어 뜻에서 알 수 있듯이 하나의 프로세스에서가 아니라 여러 프로세스가 함께 사용하는 메모리를 말합니다. 이 공유 메모리를 이용하면 프로세스끼리 통신을 할 수 있으며, 같은 데이터를 공유할 수 있습니다.

이렇게 같은 메모리 영역을 공유하기 위해서는 공유 메모리를 생성한 후에 프로세스의 자신의 영역에 첨부를 한 후에 마치 자신의 메모리를 사용하듯 사용합니다.

즉, 공유 메모리를 사용하기 위해서는 공유 메모리를 생성한 후에, 이 메모리가 필요한 프로세스는 필요할 때 마다 자신의 프로세스에 첨부를 한 후에 다른 메모리를 사용하듯 사용하면 되겠습니다.

헤더

#include <sys/type.h>
#include <sys/shm.h>

형태 void *shmat(int shmid, const void *shmaddr, int shmflg);
인수
int shmid 공유 메모리를 구별하는 식별 번호
void *shmaddr 첨부되는 어드레스 주소. 일반적으로 NULL을 지정
int shmflg

동작 옵션

shmflg 옵션 내용
SHM_RDONLY 공유 메모리를 읽기 전용으로
SHM_RND shmaddr이 NULL이 아닌 경우일 때만 사용되며, shmaddr을 반올림하여 메모리 페이지 경계에 맞춘다.

 

반환
(void *) -1 실패
이외 프로세스에 첨부된 프로세스에서의 공유 메모리 주소

예제

예제를 위해 두 개의 프로세스를 만들겠습니다. counter.c 는 공유 메모리에 1초 마다  0부터 계속 증가하는 카운터 문자열을 공유 메모리에 넣으면 show_counter.c에서 공유 메모리를 화면에 출력하도록 하겠습니다.

// counter.c   공유 메모리를 생성하고 공유 메모리에
// 카운터 문자열을 계속 갱신하여 넣습니다.

#include <stdio.h>      // printf()
#include <unistd.h>     // sleep()
#include <sys/ipc.h>
#include <sys/shm.h>

#define  KEY_NUM     9527
#define  MEM_SIZE    1024

int main( void)
{
   int   shm_id;
   void *shm_addr;
   int   count;

   if ( -1 == ( shm_id = shmget( (key_t)KEY_NUM, MEM_SIZE, IPC_CREAT¦0666)))
   {
      printf( "공유 메모리 생성 실패n");
      return -1;
   }

   if ( ( void *)-1 == ( shm_addr = shmat( shm_id, ( void *)0, 0)))
   {
      printf( "공유 메모리 첨부 실패n");
      return -1;
   }

   count = 0;
   while( 1 )
   {
      sprintf( (char *)shm_addr, "%d", count++);       // 공유 메모리에 카운터 출력
      sleep( 1);
   }
   return 0;
}

// show_counter.c   counter.c가 공유 메모리에 넣는
// 카운터 문자열을 화면에 계속 출력합니다.

#include <stdio.h>      // printf()
#include <unistd.h>     // sleep()
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>

#define  KEY_NUM     9527
#define  MEM_SIZE    1024

int main( void)
{
   int   shm_id;
   void *shm_addr;

   if ( -1 == ( shm_id = shmget( (key_t)KEY_NUM, MEM_SIZE, IPC_CREAT¦0666)))
   {
      printf( "공유 메모리 생성 실패n");
      return -1;
   }

   while( 1 )
   {
      if ( ( void *)-1 == ( shm_addr = shmat( shm_id, ( void *)0, 0)))
      {
         printf( "공유 메모리 첨부 실패n");
         return -1;
      }
      else
      {
         printf( "공유 메모리 첨부 성공n");
      }

      printf( "%sn", (char *)shm_addr);

      if ( -1 == shmdt( shm_addr))
      {
         printf( "공유 메모리 분리 실패n");
         return -1;
      }
      else
      {
         printf( "공유 메모리 분리 성공n");
      }

      sleep( 1);
   }
   return 0;
}
]$ gcc counter.c -o counter          
]$ gcc show_counter.c -o show_counter
]$ ./counter &
[1] 8077
]$ ./show_counter
공유 메모리 첨부 성공 2 공유 메모리 분리 성공 공유 메모리 첨부 성공 3 공유 메모리 분리 성공 공유 메모리 첨부 성공 4 공유 메모리 분리 성공 공유 메모리 첨부 성공 5 공유 메모리 분리 성공 공유 메모리 첨부 성공 6 공유 메모리 분리 성공 :

'Development > Linux' 카테고리의 다른 글

There was an error starting the GNOME Settings Daemon  (0) 2009.03.24
shmctl  (0) 2009.02.20
shared memory  (0) 2009.02.20
proxy server 설치하기  (0) 2009.02.17
ALSA  (0) 2008.09.11
Posted by 까 치
,

shared memory

Development/Linux 2009. 2. 20. 16:37

shmget() 함수는 공유 메모리를 생성합니다.

공유 메모리는 단어 뜻에서 알 수 있듯이 하나의 프로세스에서가 아니라 여러 프로세스가 함께 사용하는 메모리를 말합니다. 이 공유 메모리를 이용하면 프로세스끼리 통신을 할 수 있으며, 같은 데이터를 공유할 수 있습니다.

이렇게 같은 메모리 영역을 공유하기 위해서는 공유 메모리를 생성한 후에 프로세스의 자신의 영역에 첨부를 한 후에 마치 자신의 메모리를 사용하듯 사용합니다.

즉, 공유 메모리를 사용하기 위해서는 공유 메모리를 생성한 후에, 이 메모리가 필요한 프로세스는 필요할 때 마다 자신의 프로세스에 첨부를 한 후에 다른 메모리를 사용하듯 사용하면 되겠습니다.

헤더

#include <sys/ipc.h>
#include <sys/shm.h>

형태 int shmget(key_t key, int size, int shmflg);
인수
key_t key 공유 메모리를 구별하는 식별 번호
int size 공유 메모리 크기
int shmflg 동작 옵션
shmflg 옵션 내용
IPC_CREATE key에 해당하는 공유 메모리가 없다면 새로 생성한다. 만약있다면 무시하며 생성을 위해 접근 권한을 지정해 주어야 한다.
IPC_EXCL 공유 메모리가 이미 있다면 실패로 반환하며 공유 메모리에 접근하지 못한다. 이 옵션이 없어야 기존 공유 메모리에 접근할 수 있다.
반환
-1 실패
-1 이외 공유 메모리 생성 성공, 공유 메모리 식별자

예제

예제를 위해 두 개의 프로세스를 만들겠습니다. counter.c 는 공유 메모리에 1초 마다  0부터 계속 증가하는 카운터 문자열을 공유 메모리에 넣으면 show_counter.c에서 공유 메모리를 화면에 출력하도록 하겠습니다.

// counter.c   공유 메모리를 생성하고 공유 메모리에
// 카운터 문자열을 계속 갱신하여 넣습니다.

#include <stdio.h>      // printf()
#include <unistd.h>     // sleep()
#include <sys/ipc.h>
#include <sys/shm.h>

#define  KEY_NUM     9527
#define  MEM_SIZE    1024

int main( void)
{
   int   shm_id;
   void *shm_addr;
   int   count;

   if ( -1 == ( shm_id = shmget( (key_t)KEY_NUM, MEM_SIZE, IPC_CREAT¦0666)))
   {
      printf( "공유 메모리 생성 실패n");
      return -1;
   }

   if ( ( void *)-1 == ( shm_addr = shmat( shm_id, ( void *)0, 0)))
   {
      printf( "공유 메모리 첨부 실패n");
      return -1;
   }

   count = 0;
   while( 1 )
   {
      sprintf( (char *)shm_addr, "%d", count++);       // 공유 메모리에 카운터 출력
      sleep( 1);
   }
   return 0;
}

// show_counter.c   counter.c가 공유 메모리에 넣는
// 카운터 문자열을 화면에 계속 출력합니다.

#include <stdio.h>      // printf()
#include <unistd.h>     // sleep()
#include <sys/ipc.h>
#include <sys/shm.h>

#define  KEY_NUM     9527
#define  MEM_SIZE    1024

int main( void)
{
   int   shm_id;
   void *shm_addr;

   if ( -1 == ( shm_id = shmget( (key_t)KEY_NUM, MEM_SIZE, IPC_CREAT¦0666)))
   {
      printf( "공유 메모리 생성 실패n");
      return -1;
   }

   if ( ( void *)-1 == ( shm_addr = shmat( shm_id, ( void *)0, 0)))
   {
      printf( "공유 메모리 첨부 실패n");
      return -1;
   }

   while( 1 )
   {
      printf( "%sn", (char *)shm_addr);    // 공유 메모리를 화면에 출력
      sleep( 1);
   }
   return 0;
}
]$ gcc counter.c -o counter          
]$ gcc show_counter.c -o show_counter
]$ ./counter &
[1] 8077
]$ ./show_counter

'Development > Linux' 카테고리의 다른 글

shmctl  (0) 2009.02.20
shmat  (0) 2009.02.20
proxy server 설치하기  (0) 2009.02.17
ALSA  (0) 2008.09.11
Proc 파일시스템 이해하기  (0) 2008.09.11
Posted by 까 치
,