Friday, May 28, 2010

Program Library HOWTO 동적라이브러리 동적로딩

http://wiki.kldp.org/HOWTO/html/Program-Library-HOWTO/index.html

Program Library HOWTO

WheelerDavid A.

dwheeler (at) dwheeler.com

박상민

mskrap (at) kaist.ac.kr

고친 과정
고침 1.0022 March 2002
고침 0.602000-05고친이 황진희
최초 번역

이 HOWTO문서는 프로그래머들이 리눅스 상에서 어떻게 라이브러리를 만들고 사용하는지에 대한 방법을 논의한다. 이 문서는 정적 라이브러리, 공유 라이브러리, 동적 적재 라이브러리를 다룬다.

c++ 동적라이브러리 / 동적 클래스

1 http://wiki.kldp.org/wiki.php/DocbookSgml/C%2B%2B-dlopen#LOADINGCLASSES

C++ dlopen mini HOWTO

IsottonAaron

김경태

dlopen API를 이용하여 동적으로 C++ 함수와 클래스를 적재하는 방법을 설명한다.

고친 과정
고침 1.002002-06-19고친이 AI
copyright와 license부분을 앞쪽으로 옮겼다. 용어에 관한 부분을 추가했다. 약간 변화가 있었다.
고침 0.972002-06-19고친이 JYG
문장과 문법을 약간 가다듬었다.
고침 0.962002-06-12고친이 AI
참고 문헌을 추가했다. 외부 함수와 변수들의 설명을 수정했다.
고침 0.952002-06-11고친이 AI
아주 약간 개선.
고침 0.92002-06-10고친이 AI
초안 작성.

1. 소개

Unix C++ 프로그래머 사이에서 자주 발생하는 질문은 dlopenAPI를 이용하여 C++ 함수와 클래스를 적재하는 방법에 관한 것입니다.

사실 이것은 항상 간단한 것만은 아니기 때문에,약간의 설명이 필요합니다. 이 mini HOWTO에서 그것에 관한 내용을 다루고 있습니다.

이 문서를 이해하기 위해서는 C, C++,그리고dlopen API 에 대해서 어느 정도 알고 있어야 할 것입니다.

이 HOWTO의 원문은 http://www.isotton.com/howtos/C++-dlopen-mini-HOWTO/에 있습니다.


1.1. Copyright and License

이 문서 C++ dlopen mini HOWTO의 저작권은 Aaron Isotton 에게 있습니다. 자유 소프트웨어 재단에서 발표한, 1.1 혹은 그 이후 버전의 GNU Free Documentation License의 조항에 따라 이 문서를 복사하거나, 배포 혹은 변경하는 것이 허용됩니다.


1.2. 경고(Disclaimer)

이 문서의 내용으로 인한 책임은 지지 않습니다. 여기에 쓰여진 개념이나 예제 그리고 정보를 사용하여 발생한 문제에 대한 책임은 모두 당신의 책임입니다. 여기에는 당신의 시스템에 피해를 줄 수 있는 오류나 부정확한 것들이 있을수도 있습니다. 주의하여 주시기 바라며, 저는 여기에 어떠한 책임도 지지 않을 것입니다.

모든 저작권은 구체적으로 언급하지 않았다면, 그것들 각각의 소유자가 가지고 있습니다. 이 문서에 있는 용어의 사용이 어느 등록상표나 서비스 마크의 효력에 영향을 끼치는 것으로 간주해서는 안 됩니다. 특별한 제품이나 브랜드를 지명하는 것이 상품등의 추천으로 보여서는 안 됩니다.


1.3. 도와 주신분

이 문서에서, 나는 이 분들께 감사하게 되어 기쁘게 생각합니다.

  • Joy Y Goodreau 씨 는 교정에 도움을 주셨습니다.

  • D. Stimitis 씨 는 formatting 과 name mangling에 대한 몇가지 이슈를 지적해주셨고, extern "C"에 대한 몇가지 난해한 점을 지적해주셨습니다.


1.4. Feedback

이 문서에 대한 반응은 언제나 환영입니다. 당신이 추가했으면 하는 것, 의견, 비판을 다음 주소로 보내주시기 바랍니다 .


1.5. 이 문서에서 쓰인 용어들

dlopen API

dlclose, dlerror, dlopen,그리고 dlsym 함수는 dlopen(3) 매뉴얼 페이지에서 설명하고 있습니다.

이 글에서"dlopen"이라고 썼을 때는, dlopen함수 하나를 지칭하는 것이고, "dlopen API" 라고 썼을 때에는, API 전체를 지칭하는 것입니다.


2. 문제

프로그램이 실행될때, 라이브러리를 적재해야 할 때가 가끔씩 있을 것입니다. 당신이 프로그램에 들어가는 플러그인이나 모듈을 만들고 있을때 이러한 일은 종종 발생합니다.

C언어에서, 동적으로 라이브러리를 적재하는 것은 매우 간단합니다. (dlopen, dlsym 그리고 dlclose를 호출하는 것만으로 충분합니다) C++에서는 약간 더 복잡합니다. C++ 라이브러리를 동적으로 적재하는 것이 어려운 이유중에 일부분은name mangling 때문이고, 일부분은 dlopen API가 C를 염두에 두고 만들어졌기 때문에 class를 적재하는 적당한 방법을 제공하지 못하기 때문입니다.

C++에서 라이브러리를 적재하는 방법에 대해 설명하기 전에, name mangling에 대해서 자세히 살펴보고, 문제를 분석해 봅시다. 비록 당신이 name mangling에 관심이 없더라도, 나는 당신이 그것에 대한 설명을 읽기를 바랍니다. 왜냐하면 그것은 당신이 왜 문제가 발생하고 어떻개 해결해야 하는지를 이해하는데 도움을 주기 때문입니다.


2.1. Name Mangling

모든 C++프로그램(혹은 라이브러리나 Object 파일)에서, 모든 non-static 함수는 이진 파일에서 symbol로 표현됩니다. 이러한 symbol들은 프로그램(혹은 라이브러리나 Object 파일)에서, 유일하게 함수를 확인하는, 특수한 문자열입니다.

C에서는, symbol의 이름은 함수의 이름과 동일합니다. strcpy의 symbol은 strcpy입니다. C에서는 두개의 non-static 함수가 같은 이름을 가질 수 없기 때문에 이것이 가능합니다.

C++은 오버로딩을 허용하고 (같은 이름을 가지지만 인자가 다른 함수를 정의할 수 있다), C가 가지고 있지 않은 여러가지 특징들 — Class, member function, exception specifications — 을 가지고 있기 때문에, 그냥 단순히 함수 이름을 symbol 이름으로 쓸 수는 없습니다. 이 문제를 해결하기 위해서, C++에서는 name mangling(이름 엉망으로 만들기)이라는 것을 사용합니다. 이것은 함수이름과 모든 필요한 정보 모두를(인자의 크기나 갯수와 같은) 컴파일러만이 알아볼 수 있는 이상한 문자열로 바꿔버립니다. 예를 들어 foo라는 함수가 있다면, name mangling에 의해 foo@4%6^로 바뀔 것입니다.

name mangling과 관련하여 생기는 문제점의 하나는 C++ 표준 (현재는 [ISO14882])이 그 방법을 정의해 놓지 않았다는 점입니다. 이것은 모든 컴파일러들마다 자기만의 방법 으로 name mangling을 한다는 것을 의미합니다. 어떤 컴파일러는 name mangling 알고리즘이 버전에 따라 다르기도 합니다. (g++ 2.x 와 3.x에서 뚜렷하게 드러납니다.) 비록 당신이 특정한 컴파일러가 어떻게 이름을 바꾸는지 이해했다고 해도(그래서 dlsym을 통해 그 함수를 적재할 수 있게 된다고 해도), 그것은 아마 그 특정한 컴파일러에서만 효과가 있을 것이고, 다음 버전의 컴파일러에서는 이미 사용할 수 없을지도 모릅니다.


2.2. Classes

dlopen API 의 또다른 문제는, 이들이함수를 적재하는 것만을 지원하고 있다는 점입니다. 하지만 C++에서는 라이브러리가 종종 당신이 프로그램에서 쓰고자 하는 Class를 노출시키고는 합니다. 분명히, 클래스를 사용하기 위해서 당신은 그것의 인스턴스를 만들어야 하지만, 그것은 쉽지 않습니다.


3. 해결 방법

3.1. extern "C"

C++에는 extern "C"라는, C binding으로 함수를 정의하는 특별한 키워드가 있습니다. extern "C"로서 선언된 함수는 C처럼 함수의 이름을 symbol의 이름으로 사용합니다. 이러한 이유로, 멤버함수가 아닌 함수들만이 extern "C"로서 선언될 수 있고, 이러한 함수들은 오버로딩을 할 수 없습니다.

이런 심한 제한이 있지만, extern "C"함수는 C 함수처럼 dlopen을 써서 동적으로 적재할 수 있기 때문에 매우 유용합니다.

이것은 extern "C"로 선언된 함수가 C++ 코드를 포함할 수 없다는 것을 의미하는 것이아닙니다. 이런 함수는 어느 종류의 인자라도 받을 수 있고, C++의 특징을 쓸 수 있습니다.


3.2. 함수를 적재하는 법

C++에서 함수는 dlsym을 통해 C처럼 적재됩니다. 당신이 적재하고자 하는 함수는 symbol의 이름이 엉망으로 되지 않도록(name mangling이 이루어지지 않도록), extern "C"로서의 자격을 갖춰야 할 것입니다.

예 1. 함수를 적재하기

main.cpp:

#include  #include    int main() {     using std::cout;     using std::cerr;      cout << "C++ dlopen demo\n\n";      // open the library     cout << "Opening hello.so...\n";     void* handle = dlopen("./hello.so", RTLD_LAZY);          if (!handle) {         cerr << "Cannot open library: " << hello =" (hello_t)">

hello.cpp:

#include   extern "C" void hello() {     std::cout << "hello" << '\n'; }

hello라는 함수는 hello.cpp에서 extern "C"로 선언되었습니다. 이것은 main.cpp에서 dlsym을 사용하여 적재할 수 있습니다. 함수는 extern "C" 로서의 자격을 갖추어야 합니다. 그렇지 않다면 우리는 hello 함수의 symbol의 이름을 알 수 없을테니까요.

주의

extern "C"의 선언에는 두가지의 다른 형태가 있습니다: 하나는 위에서 썼던extern "C"의 방법이고, 또 다른 하나는 extern "C" { … }형태로 중괄호 사이에 선언이 들어가는 방법입니다. 첫번째(inline)형태는 extern 연결과 C 언어 연결을 갖습니다. 두번째 형태는 C언어 연결에만 영향을 끼칩니다. 따라서 아래의 두 가지 선언 방법은 동일한 것입니다.

extern "C" int foo; extern "C" void bar();             
extern "C" {      extern int foo;      extern void bar();  }

externextern이 아닌 함수의 선언에는 차이가 없기때문에, 당신이 어떠한 변수도 선언하지 않는 한 문제가 없습니다. 당신이 변수를 선언한다면,다음의 두 선언 방법은 같지 않다는 것을 명심해야 합니다.

extern "C" int foo;
extern "C" {     int foo; }

좀 더 자세한 설명을 원하시면, paragraph 7에 주의를 기울여 [ISO14882]의 7.5를 읽어보시거나, [STR2000]의 paragraph 9.2.4.를 참조해주십시오.

외부 변수로 무언가를 하기 전에, see also부분에 적혀 있는 문서들을 정독해주시기 바랍니다.


3.3. 클래스를 적재하는 법

클래스를 적재하는 것은 좀 더 어렵습니다. 왜냐하면 우리는 클래스의인스턴스를 필요로 하지, 함수에 대한 포인터를 필요로 하는게 아니기 때문입니다.

클래스가 실행파일에 정의되어 있지 않은데다가,(몇몇 상황에서는) 클래스의 이름조차도 알 수 없기 때문에, 우리는 new 를 이용하여 클래스의 인스턴스를 생성할 수 없습니다.

이것은 다형성(polymorphism) 을 통해 해결할 수 있습니다. 우리는 기반 클래스, 가상의 멤버를 가지고 있는 인터페이스클래스를 실행파일내에 선언하고, 실제 구현부의 클래스를 모듈 에 선언합니다. 일반적으로 인터페이스 클래스는 추상적입니다. (클래스가 순수가상함수(pure virtual function)을 가지고 있을때 클래스가 추상적이라고 합니다.)

클래스의 동적인 적재는 일반적으로 플러그인 — 명확하게 정의된 인터페이스를 보여주어야 하는 — 에 쓰이기 때문에, 우리는 어쨌거나 인터페이스 클래스와 거기서 파생된 구현부 클래스를 정의해야 합니다.

다음으로,모듈 안에Class factory function이라는 두개의 도움을 주는 함수를 추가로 선언해야 합니다. 이 함수 중 하나는 클래스의 인스턴스를 만들고 그것의 포인터를 반환하는 역할을 하고, 또 다른 하나의 함수는 factory에서 만들어진 함수의 포인터를 받아 그것(클래스의 인스턴스)를 파괴하는 역할을 합니다. 이 두 함수는 extern "C"의 자격을 가지고 있어야 합니다.

클래스를 모듈에서 쓰기 위해서,두개의 factory function을 우리가 hello함수를 적재했던 것처럼dlsym을 사용하여 적재하십시오.그럼 우리는 우리가 원하는 만큼의 인스턴스를 생성할수도 있고, 파괴할 수도 있습니다.

예 2. 클래스를 적재하는 법

여기서 우리는 일반적인다각형을 인터페이스로 하고, 삼각형을 구현부분으로 할 것입니다.

main.cpp:

#include "polygon.hpp" #include  #include   int main() {     using std::cout;     using std::cerr;      // load the triangle library     void* triangle = dlopen("./triangle.so", RTLD_LAZY);     if (!triangle) {         cerr << "Cannot load library: " << create_triangle =" (create_t*)" destroy_triangle =" (destroy_t*)" poly =" create_triangle();">set_side_length(7);         cout << "The area is: " <<>area() << '\n';      // destroy the class     destroy_triangle(poly);      // unload the triangle library     dlclose(triangle); }

polygon.hpp:

#ifndef POLYGON_HPP #define POLYGON_HPP  class polygon { protected:     double side_length_;  public:     polygon()         : side_length_(0) {}      void set_side_length(double side_length) {         side_length_ = side_length;     }      virtual double area() const = 0; };  // the types of the class factories typedef polygon* create_t(); typedef void destroy_t(polygon*);  #endif

triangle.cpp:

#include "polygon.hpp" #include   class triangle : public polygon { public:     virtual double area() const {         return side_length_ * side_length_ * sqrt(3) / 2;     } };   // the class factories  extern "C" polygon* create() {     return new triangle; }  extern "C" void destroy(polygon* p) {     delete p; }

클래스를 적재할때 주의해야 할 점이 몇가지 있습니다:

  • 당신은 인스턴스를 생성하는 함수와 파괴하는 함수를 모두 제공해야 합니다. 또 당신이 인스턴스를 파괴할때에는 실행파일 내에서 delete를 이용해서 지우지 마시고 ,항상 모듈로 넘겨서 주시기 바랍니다. 이는 C++ 에서 newdelete 가 오버로딩 될 수 있기 때문입니다. 이 경우에 서로 맞지 않는 newdelete 가 호출될 수 있고, 그렇게 되면 메모리 누수나 segmentation fault가 일어날 수도 있습니다.이것은 모듈과 실행파일을 링크할때 서로 다른 표준 라이브러리를 사용했을 경우에도 적용됩니다.

  • 인터페이스 클래스의 소멸자는 구현부 클래스에 소멸자가 없는경우를 제외하고는 가상(virtual)이어야 합니다. 그렇지 않으면 그것은 호출되지 않을 것이고, 메모리 누수나 segmentation fault가 일어날 수도 있습니다.


4. See Also


서지사항

ISO14482 ISO/IEC 14482-1998 — The C++ Programming Language. http://webstore.ansi.org/에서 PDF로 이용가능합니다.

STR2000 Stroustrup Bjarne The C++ Programming Language, Special Edition. ISBN 0-201-70073-5. Addison-Wesley.










1 이유

다양한 애플리케이션에 사용가능한 로그분석 프로그램을 만들려고 한다. 이러한 프로그램의 경우 어떤 애플리케이션에서 사용할지 알 수 없기 때문에, 원본소스에는 수정할 필요 없이 로그분석 알고리즘만 적재가능하도록 만들 필요가 있다.

가장좋은 방법은 main 코드와 알고리즘을 분리시키는 것으로, 이것은 라이브러리의 동적적재를 이용해서 달성가능 하다. 동적적재는 라이브러리문서에 언급되어 있다.

즉 알고리즘을 플러그인 형태로 적재하는 기술인데, 여기에 더해서 C++의 클래스를 동적으로 적재시킬 수 있기를 원했다. 그렇다면, 클래스의 추상화를 이용해서, 좀더 일관된 개발자 인터페이스를 제공할 수 있을 것이기 때문이다.

그러므로 다음의 두가지를 달성하는게 주요 목표가 될 것이다.
  1. 로그분석 알고리즘을 플러그인 형태로 적재할 수 있도록 한다.
  2. 클래스를 적재함으로써, 일관된 개발자 인터페이스를 제공한다.

이것은 전술패턴의 구현으로 볼 수 있을 것이다.

2 C++로 작성된 라이브러리사용의 문제

C++에서 클래스의 동적적재 가능성에 대해 생각해 본다.

C에서의 라이브러리의 동적적재는 명료하다. 이에 대한 내용은 라이브러리만들기 문서를 참고한다.

C++에서는 name mangling 때문에 dlopen()을 이용해서 라이브러리를 적재하는데, 어려움이 있다. 애시당초 dlopen()이 C++을 염두에 두지 않고 만들었다는 것도 문제일 것이다.

C와 C++은 함수를 가리키기위한 symbol 테이블을 가지고 있다. 어떤 함수를 호출하면 symbol 테이블을 뒤져서, 이진파일 내에서 함수의 원본의 위치를 알아내어서 읽어들이고 실행하는 것으로 묘사할 수 있다.

C는 symbol이 하나의 함수와 대응한다. 그렇지만 C++은 overloading으로 인하여서, 함수이름과 심볼이 일치하지 않는 경우가 발생한다. 이름은 같지만 인자가 다른 함수가 대표적인 경우다. 그러므로, C++로 공유라이브러리를 만들고자 할경우, 이 함수는 반드시 유일하다는 것을 컴파일러에게 알려줄 필요가 있다.

3 extern "C" 를 이용한 해결

오버로딩으로 인해서 발생하는 문제는 extern "C"를 이용하면 해결할 수 있다. extern "C" 는 해당 함수가 심볼이름과 일치될 것이라는 것을 알려준다. 이를테면 C 함수와 마찬가지로 사용하겠다는 의미다. 대신 C++의 기능인 overload등은 사용할 수 없게 된다.

예컨데 hello란 함수가 있다면, 다음과 같이 extern "C"를 이용해서 정의할 수 있다.
#include     using namespace std;  extern "C" void hello()  {      cout << "hello" << "\n";      }   

4 class의 동적적재

exern "C"를 이용해서, 오버로딩이 필요없는 함수를 적재시키는 법에 대해서 알아봤다. 그렇다면, class는 어떨까.

안타깝게도 class는 dlopen()을 이용해서 호출할 수가 없다. 애초에 dlopen이 class를 염두에 두고 만들어진게 아니기 때문이다. 가장 일반적으로 사용할 수 있는 방법은 class에 대한 포인터를 넘겨주는 factory 함수를 만들고, 이 포인터를 이용해서 메서드를 호출하는 방법일 것이다.

다음은 factory 함수를 이용해서 클래스를 호출하는 예제 프로그램이다. 프로그램이름은 main.cc로 하겠다.
#include   #include   #include   #include   #include   #include "mymodule1.h"    int main(int argc, char **argv)  {    void *handle;      char *error;    Test *LTest;      handle = dlopen("libmymodule.so", RTLD_LAZY);    if (!handle)    {      perror("Open Library\n");      exit(0);    }      // Func_Init    init_t* init_myFunc = (init_t *)dlsym(handle, "Func_Init");    if ((error = dlerror()) != NULL)    {      printf("ERROR : %s\n",error);      exit(0);    }    LTest = init_myFunc();    LTest->Count();    LTest->Count();    LTest->Print();      // Func_destory    destroy_t *destroy_myFunc = (destroy_t *)dlsym(handle, "Func_destory");    destroy_myFunc(LTest);  }   

mymodule1.h로 class Test가 선언되어 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 
#ifndef _MYMODULE_H_  #define _MYMODULE_H_    #include   #include     using namespace std;    class Test  {    private:      int count;    public:      Test();      ~Test()      {        printf("Destory\n");      }      void Count();      void Print();  };  extern "C" class Test* Func_Init();  extern "C" void Func_destory(class Test *aTest);    typedef void destroy_t(Test*);  typedef Test* init_t();  #endif   
Funic_initFunc_destory라는 factory 함수가 선언되어있다.

다음은 Test 클래스와 factory 함수가 정의되어 있는 코드로 파일명은 mymodule1.cc다.
#include "mymodule1.h"    Test::Test()  {      printf("Init\n");      count=0;  }    void Test::Count()  {      count++;  }    void Test::Print()  {      printf("Count is %d\n", count);  }    class Test* Func_Init()  {      Test *myTest;      myTest = new Test;      return myTest;  }    void Func_destory(class Test *aTest)  {      delete aTest;  }    void Func_Test(class Test *aTest)  {      aTest->Count();      aTest->Print();  }   

다음은 컴파일 방법이다. 먼저 mymodule1.cc를 공유라이브러리형태로 컴파일 한다.
# g++ -fPIC -c mymodule1.cc  # g++ -shared -W1,-soname,libmymodule.so.1 -o libmymodule.so.1.0.1 mymodule1.o   

main.cc를 컴파일 한다.
g++ -o main main.c -ldl -lmymodule   
일반적인 동적적재의 경우 함수원형이 필요 없지만 class의 경우에는 메서드를 호출해야 하기 때문에 -l옵션을 이용해서 메서드에 대한 정의가 있는 라이브러리 명시적으로 지시해줘야 한다.

만들어진 프로그램을 실행시켜 보자.
$ ./main  Init  Count is 2  Destory   
제대로 실행됨을 알 수 있다.

5 다형성의 구현

factory 함수를 이용해서 클래스를 로딩할 수 있는 방법을 터득했으니, 이제 로그분석 프로그램 제작에 대한 계획을 세워보기로 하자.

이 로그분석 프로그램은 다음과 같은 사항들을 만족시켜야 한다.
  1. 다양한 로그에 대응할 수 있도록 플러그인형식으로 적재할 수 있어야 한다.
  2. 프로그래머에게 공통의 인터페이스를 제공할 수 있어야 한다.
1번 요구사항은 라이브러리의 동적적재를 이용해서 해결할 수 있다. 즉 설정파일을 만든다음에, 해당 로그파일에 대응되어서 적재할 라이브러리를 명시해주면 된다. 이것에 대한 간단한 예는 라이브러리의 사용에 언급되어 있으니, 응용하는데 문제없을 것이다.

2번 요구사항은 클래스를 적재시키고, 클래스의 메서드를 가상함수화 하는 것으로 달성할 수 있을 것이다. 일종의 Interface 클래스를 만들고, 개발자는 Interface의 메서드를 구현하는 방식으로 필요한 모듈을 개발하는 것이다.

6 예전 방식 구현

예전에도 로그분석 비스무레한 프로그램을 만들었던 적이 있다. 보안로그분석프로그램이였는데, 보안장비로 부터 syslog, snmp 혹은 전용socket으로 부터 보안로그를 받아서 분석하고, 이벤트를 생성하는 프로그램이였다. 기본개념은 DOS 공격검사 프로그램의 제작에 소개된 바가 있다.

기본 시스템/네트워크 구성은 다음과 같다.

dos.png

보안장비는 다양한 종류의 보안소프트웨어가 설치되어 있으며, 로그 또한 전혀표준화 되어 있지 않으며, 나름대로의 정책을 가지고 만들어진다. 만약 새로운 장비가 추가된다면, 분석소프트웨어는 해당 장비의 보안로그를 분석할 수 있는 기능이 추가되어야 한다.

이 프로그램은 다음의 조건을 만족할 수 있어야 했다.
  1. 새로운 보안장비 혹은 새로운 보안 제품이 추가되면, 다음과 같은 이유로 새로운 분석모듈을 개발해야 한다.
    • 보안로그가 표준화 되어 있지 않았기 때문
    • plain text, binary 데이터 혹은 DB로 log를 저장하는 등 방식의 차이
    • snmp, syslog, 전용 socket 등 다양한 통신 방법
  2. 가능한 main 코드는 수정되지 않아야 한다.
  3. 모듈의 개발은 일관성이 담보될 수 있어야 한다.
위의 요건들을 만족시키기 위해서 다음의 방법을 사용했다. 일종의 전술패턴의 응용이라고 볼 수 있을 것이다.
  1. 각 제품에 대응되는 라이브러리를 생성한다. 10개의 제품의 로그를 분석해야 한다면, 10개의 라이브러리가 만들어질 것이다.
  2. {해당장비 => 해당장비의 로그를 분석할 수 있는 알고리즘이 포함된 라이브러리}를 선택하도록 전술을 구사한다.
이 방식은 함수포인터를 이용한 프로토콜 처리에 비슷하게 구현되어 있으니 참고하기 바란다.

이 방식은 만족시켜야할 조건중 1번과 2번은 어느정도 만족시킬 수 있지만, 3번을 만족시키지는 못했다. 코드가 객체지향적이지 않았기 때문에, 즉C로 개발되었기 때문이다. 물론 좀더 노력과 시간을 들였다면, 3번을 만족시킬 수 있는 프로그램의 제작이 가능했겠지만, 그때는 그럴만한 실력을 갖추지 못했다.

7 방법의 개선

그래서 class의 가상함수를 이용해서 개발자 인터페이스를 만들고, 이 인터페이스를 상속받아서 실제 구현을 하도록 하는 방법을 생각했다. 여기에서는 대략적인 개념만 소개하는 정도로 하겠다.

다음과 같이 순수가상함수를 포함하는 Interface 클래스를 만들도록 한다. interface.h로 하겠다.
#ifndef _INTERFACE_H_  #define _INTERFACE_H_    class Log  {    private:      int data;    public:      virtual int Create()=0;      virtual int Anly()=0;      virtual int Read()=0;      virtual int Destroy()=0;      virtual ~LogAnly()      {      }  };    #endif   
  • 이제 개발자는 모듈의 작성시 위의 Log클래스를 상속받고, 각 가상함수를 실구현하면 된다.
    • Create : 객체를 생성한다.
    • Anly : 실제 분석을 한다.
    • Read : 분석된 데이터를 읽어들인다.
    • Destroy : 객체를 파괴한다.
다음은 실구현을 포함한 코드다.
#include   #include     class TestLogAnly : public Log  {    private:    struct _CountData    {      int count1;      int count2;    };    _CountData CData;    Config *Cfg;    public:      TestLogAnly();      int Create(char *);      int Anly();      int Read();      int Destroy();      ~TestLogAnly();  };    using namespace std;  int TestLogAnly::Create()  {    cout << "Create " << href="http://www.joinc.co.kr/modules/moniwiki/wiki.php/manSearch?google=none&name=TestLogAnly::Destroy" style="color: rgb(51, 153, 204); text-decoration: none; ">TestLogAnly::Destroy()  {    cout << "Module Anly Destroy" << href="http://www.joinc.co.kr/modules/moniwiki/wiki.php/manSearch?google=none&name=TestLogAnly::Anly" style="color: rgb(51, 153, 204); text-decoration: none; ">TestLogAnly::Anly()  {    CData.count1 = 100;    CData.count2 = 200;    cout << "Log Anly" << href="http://www.joinc.co.kr/modules/moniwiki/wiki.php/manSearch?google=none&name=TestLogAnly::Read" style="color: rgb(51, 153, 204); text-decoration: none; ">TestLogAnly::Read()  {    cout << "ReadData count 1 : "<< href="http://www.joinc.co.kr/modules/moniwiki/wiki.php/manSearch?google=none&name=memset" style="color: rgb(51, 153, 204); text-decoration: none; ">memset((void *)&CData, 0x00, sizeof(CData));  }    // factory 함수의 선언  extern "C" TestLogAnly *Obj_Create();  extern "C" void Obj_Destroy(TestLogAnly *);    TestLogAnly *Obj_Create()  {          TestLogAnly *rtv;          rtv = new TestLogAnly();          return rtv;  }    void Obj_Destroy(TestLogAnly *aLog)  {          delete aLog;  }   
이것으로 동적적재에도 클래스의 가상화, 추상화, 은닉을 적용할 수 있게 되었다.

Thursday, May 27, 2010

gcc library 만들기

http://poongcha.blogspot.com/2009/06/gcc-library.html

2009년 6월 6일 토요일

gcc library 만들기


정적 라이브러리 만들기

1.Object 만들기
$ g++ -c Squawk.cpp Cat.cpp Dog.cpp Lion.cpp Car.cpp

2.라이브러리 만들기
$ ar cr libSquawk.a Squawk.o Cat.o Dog.o Car.o Lion.o

3.라이브러리 인덱스 만들기 (optional)
ranlib == ar -s와 같으며, 만들어진 index는 nm -s를 통해 확인 가능
$ ranlib libSquawk.a

정적 라이브러리 사용하기

1.컴파일
라이브러리 Header 파일 디렉토리 지정 필요
$ g++ -c -I ../CoreSquawk main.cpp

2.링크
라이브러리 및 라이브러리 디렉토리 지정 필요
$ g++ -o TestSquawk.exe main.o -L ../CoreSquawk -lSquawk

공유 라이브러리 만들기

하나 이상의 프로세스에서 사용되는 경우 메모리 양과 실행 파일 크기 줄여줌, 개발 과정 용이, 라이브러리의 변경시에도 대부분 애플리케이션 재컴파일 불필요

1.재배치가능한 Object (Position Independent Code) 만들기
$ g++ -fPIC -c Squawk.cpp Cat.cpp Dog.cpp Lion.cpp Car.cpp

2.공유 라이브러리 만들기
$ gcc -shared -o libSquawk.so Squawk.o Cat.o Dog.o Car.o Lion.o

공유 라이브러리 사용하기

1.컴파일 라이브러리 Header 파일 디렉토리 지정 필요
$ g++ -c -I ../CoreSquawk main.cpp

2.링크 라이브러리 및 라이브러리 디렉토리 지정 필요
$ g++ -o TestSquawk.exe main.o -L ../CoreSquawk -lSquawk

꼭 정적인 라이브러리를 링크 하고자 할 때
$ g++ -o TestSquawk.exe main.o ../CoreSquawk/libSquawk.a

ldd 명령: 실행 프로그램이 사용하는 공유 라이브러리 나열
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/user/CoreSquawk
$ ldd TestSquawk.exe

참고자료 :
윈도우에서 유닉스로 이식하기 http://www.ibm.com/developerworks/kr/library/au-porting/index.html

0 개의 댓글:

댓글 쓰기

Wednesday, May 26, 2010

쓰레드 종료 상태

이번 글은 쓰레드 종료와 관련된 얘기를 다루고 있다.

본문 출처 : joinc yundream님

차례
1절. 소개
2절. Thread 취소(cancellation)와 종료
2.1절. Thread 취소
2.1.1절. 쓰레드 취소상태의 설정
2.1.2절. 쓰레드 취소종류의 설정
2.1.3절. 취소지점
2.2절. 쓰레드 기본 취소상태와 취소 종류
2.3절. 쓰레드 종료시 자원정리
2.3.1절. 쓰레드 종료시 자원해제
2.4절. 총정리
3절. 참고문헌

--------------------------------------------------------------------------------

1절. 소개
쓰레드에 대해서 학습을 해본적이 있다면 Thread 취소와 종료에 대한 내용은 뻔한 것 아니냐 라고 생각할 수 있을 것이다. 하지만 이 문서를 읽어 보면 왜 별도의 문서를 만들어서 종료와 취소에 대해서 다루었는지 이해하게 될 것이다.


--------------------------------------------------------------------------------

2절. Thread 취소(cancellation)와 종료
쓰레드는 제어가능한 객체로 필요에 따라 생성시킬 수 있듯이 필요에 따라서 중단 시킬 수도 있다. 이 쓰레드 중단이라는 것이 매우 단순한 행위라고 생각되지만 생각처럼 그렇게 단순한 행위가 아니다. 멀티 쓰레드 프로그램이라면 쓰레드간 동기화를 위해서 조건변수 뮤텍스등 을 사용하고 있을 것이며, 여러가지 공유 자원들 역시 가지고 있을 것이다.

몇명의 인원이 같이 참가해서 진행하는 프로젝트가 있다고 생각해 보자. 그중 한명이 프로젝트에서 빠지면 나머지 인원이 프로젝트를 진행하는데 문제가 생기지 않도록 이런 저런 뒷수습을 해주는게 매우 중요하다. 쓰레드 역시 마찬가지로 중단(종료)시 뒷수습을 해주는 것은 매우 중요한 일이다. 이 문서는 쓰레드 취소에 관련된 내용과 쓰레드 종료시 신경써야될 (자원정리 와 같은)것들에 대해서 알아보도록 한다.


--------------------------------------------------------------------------------

2.1절. Thread 취소
멀티 쓰레드 프로그램에서 특정 쓰레드를 중단 시키고자 할때를 위해서 Pthread는 ptread_cancel()이라는 함수를 제공한다. int pthread_cancel(pthread_t thread);


이 함수는 인자로 주어진 쓰레드 식별번호 thread를 가지는 쓰레드를 중지시킨다. 명확히 말하자면 쓰레드를 중지 시키는게 아니고 쓰레드에 취소 요청을 하는 것으로 봐야 한다. 취소 요청을 받은 쓰레드가 어떻게 반응 할런지는 요청을 받은 쓰레드의 취소 상태 설정에 의존한다. 취소 요청을 받은 쓰레드는 취소 상태에 의해서 필요한 작업을 한 후 종료 하게 된다. 취소 요청을 받아서 종료하는 쓰레드는 pthread_exit(PTHREAD_CANCELED)를 호출하고 종료한다.

pthread_cancel()에 의해서 취소가 통보된 쓰레드는 쓰레드 취소 상태의 설정에 따라서 취소 요청을 무시할 수도 취소지점(cancellation point) 지점까지 수행한뒤에 종료 될수도 있기 때문이다.

쓰레드 취소와 종료는 엄연히 다르다는 것을 이해하기 바란다. 그렇지 않으면 앞으로 문서의 내용을 읽는데 헛갈릴 수 있다.


--------------------------------------------------------------------------------

2.1.1절. 쓰레드 취소상태의 설정
쓰레드가 pthread_cancel()에 의해서 취소요청을 받았을 때 어떻게 반응할런지를 결정하는 쓰레드 취소상태는 여러가지 방법에 의해서 결정된다. 취소 상태는 pthread_setcancelstat() 함수에 의해 결정한다. int pthread_setcancelstate(int state, int *oldstate);


첫번째 인자인 state는 새로운 취소상태를 설정하기 위해서 사용된다. 두번째 인자인 oldstate는 이전의 취소상태의 설정값을 받아오기 위해서 사용된다. 이 함수는 다음과 같이 사용할 수 있다.
  1. int old_cancel_state;
  2. pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancel_state);
  3. pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_cancel_state);



만약 이전의 취소상태 설정값이 필요 없다면 old_cancel_state대신 NULL을 사용하면 된다. 위의 사용에서 처럼 쓰레드는 PTHREAD_CANCEL_DISABLE와 PTHREAD_CANCEL_ENABLE 둘중 하나의 취소상태를 가질 수 있다.

만약 PTHREAD_CANCEL_ENABLE 상태라면 쓰레드는 취소요청을 받아들이고 취소지점까지 진행한다음 취소 지점을 벗어나야지 종료한다. ENABLE 상태일 경우 별도로 취소지점까지 진행한다음 종료할 것인지 아니면 바로 종료할 것인지를 pthread_setcanceltype() 를 통해 지정할 수 있다. 만약 PTHREAD_CANCEL_DISABLE 상태라면 취소요청을 받은 후 취소지점까지의 진행을 하지 않고 바로 종료한다.


--------------------------------------------------------------------------------

2.1.2절. 쓰레드 취소종류의 설정
쓰레드 취소상태가 PTHREAD_CANCEL_ENABLE인 경우 취소의 종류를 결정할 수 있다. 취소종류의 결정은 pthread_setcanceltype()을 통해서 이루어진다. int pthread_setcanceltype(int type, int *oldtype);


취소종류는 type를 통해서 결정된다. PTHREAD_CANCEL_ASYNCHRONOUS와 PTHREAD_CANCEL_DEFERRED 둘 중 하나를 선택할 수 있다. 전자일 경우 바로 종료하며, 후자의 경우 취소지점을 벗어날 때까지 기다리게 된다. oldtype를 이용해서 이전 취소타입을 얻어 올 수 있다. NULL이라면 받아오지 않는다. 이 함수는 당연하지만 취소상태가 PTHREAD_CANCEL_DISABLE 라면 의미 없는 함수다.


--------------------------------------------------------------------------------

2.1.3절. 취소지점
쓰레드에게 취소요청이 왔다고 해서 무조건 취소해 버리면 문제가 생길 수도 있다. 어떤 일을 처리하고 있는 중에 취소요청이 전달했는데, 별로 중요하지 않는 (무시해도 될만한) 일이라면 중단후 바로 취소해도 되겠지만 중요한 일을 처리하는 중이라면 일을 처리한후 종료 해야 할것이다. 이 마지 노선이 취소지점이다.

취소지점으로 설정될 수 있는 영역은 다음과 같다.
  1. pthread_join(3)
  2. pthread_cond_wait(3)
  3. pthread_cond_timedwait(3)
  4. pthread_testcancel(3)
  5. sem_wait(3)
  6. sigwait(3)



pthread_setcancelstate()함수에 의해서 PTHREAD_CANCEL_ENABLE 상태로 되어 있다면 취소지점을 무시하고 즉시 종료 된다. PTHREAD_CANCEL_DISABLE로 되어 있다면 위의 취소지점을 벗어날 때까지 기다린다. 즉 취소요청을 받은 쓰레드가 pthread_cond_wait()에서 조건변수를 기다리는고 있다면 조건변수 를 받을 때까지 취소를 유보하게 된다.


--------------------------------------------------------------------------------

2.2절. 쓰레드 기본 취소상태와 취소 종류
별다른 설정이 없을 경우 pthread_create()로 만들어 지는 쓰레드는 PTHREAD_CANCEL_ENABLE, PTHREAD_CANCEL_DEFERRED로 상태로 생성된다.


--------------------------------------------------------------------------------

2.3절. 쓰레드 종료시 자원정리
pthread_cancel()등을 통해서 종료 통보를 받은 쓰레드는 종료하기 전에 여러가지 일을 해주어야 할 것이다. 뭐 간단한 쓰레드라면 관계 없겠지만 복잡하게 얽혀 있는 멀티 쓰레드 프로그램이라면 이런 저런 정리해줘야 할 것들이 많을 것이다.


--------------------------------------------------------------------------------

2.3.1절. 쓰레드 종료시 자원해제
쓰레드에서 malloc()등을 호출해서 메모리 공간을 확보했다거나 DB나 파일, 소켓등을 열어서 작업했다면 반드시 이들 자원을 해제시켜줘야 한다. 간단하게 생각하자면 쓰레드 종료시점에서 free(), close(), DB라면 이런 저런 정리를 해주면 될것이다. 그러나 pthrad_cancel()등에 의해서 작업중간에 요청을 받았다면 그리 간단한 문제가 아니다. 쓰레드 마지막까지 루틴을 진행할 수 없기 때문이다. 이럴 경우를 대비해서 pthread_cleanup_push(), pthread_cleanup_pop()와 같은 함수를 제공한다.

이 함수들을 이용해서 쓰레드가 종료할 때 호출해야할 함수를 지정할 수 있다. 그러므로 프로그래머는 이들 함수에 자원해제와 같은 필요한 코드를 넣어두기만 하면 된다. 이들 함수에 대한 자세한 내용은 Pthread_API를 참고하기 바란다.


--------------------------------------------------------------------------------

2.4절. 총정리
지금까지의 내용을 예제를 통해서 정리해보도록 하자.
  1. #include
  2. #include
  3. #include
  4. #include
  5. #include
  6. // 쓰레드 종료시 호출될 함수
  7. void clean_up(void *);
  8. // 쓰레드 함수
  9. void *thread_func(void *);
  10. pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  11. pthread_mutex_t lmu = PTHREAD_MUTEX_INITIALIZER;
  12. int main(int argc, char **argv)
  13. {
  14. pthread_t pt;
  15. pthread_create(&pt, NULL, thread_func, NULL);
  16. // 생성된 쓰레드 pt에 취소 요청을 보낸다.
  17. pthread_cancel(pt);
  18. // 5초를 쉰 후에 시그널을 보낸다.
  19. sleep(5);
  20. pthread_cond_signal(&cond);
  21. // join후 종료한다.
  22. pthread_join(pt, NULL);
  23. printf("exit\n");
  24. exit(1);
  25. }
  26. // 쓰레드 종료시 효출될 함수
  27. // 여기에 자원해제루틴을 입력할 수 있을 것이다.
  28. void clean_up(void *arg)
  29. {
  30. printf("Thread cancel Clean_up function\n");
  31. }
  32. void *thread_func(void *arg)
  33. {
  34. // DISABLE 상태다.
  35. // 이경우 쓰레드에 대해서 취소 요청이 들어오면
  36. // 바로 취소된다.
  37. pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
  38. // 쓰레드 종료시 호출될 함수 등록
  39. pthread_cleanup_push(clean_up, (void *)NULL);
  40. while(1)
  41. {
  42. pthread_mutex_lock(&lmu);
  43. printf("THREAD cond wait\n");
  44. pthread_cond_wait(&cond, &lmu);
  45. printf("NO WAIT COND\n");
  46. pthread_mutex_unlock(&lmu);
  47. }
  48. printf("EXIT\n");
  49. pthread_cleanup_pop(0);
  50. }



main 쓰레드는 thread_func를 실행시킨다. thread_func는 pthread_cond_wait()에서 신호가 발생하기를 기다리게 된다. 잠시후 main 쓰레드는 pthread_cancel()을 이용해서 thread_func 쓰레드에 취소 요청을 하게 된다. 위의 경우 thread_func쓰레드는 취소상태가 PTHREAD_CANCEL_DISABLE로 되어 있기 때문에 취소 지점등은 부시하고 바로 종료된다. 이때 pthread_cleanup_push()에 등록된 clean_up함수가 실행되는걸 확인할 수 있을 것이다.

PTHREAD_CANCEL_ENABLE로 되어 있을 경우 취소 요청을 받아들이기 때문에 취소 요청이 들어오더 라도 thread_func가 현재 종료 지점에 머물러 있기 때문에 곧바로 종료하지 않는다. main 쓰레드에서 pthread_cond_signal()를 호출해서 thread_func가 cond_wait상태를 벗어난 후 종료된다. 물론 cleanup 함수도 실행된다.


--------------------------------------------------------------------------------

3절. 참고문헌


pthread API 레퍼런스
: http://www.joinc.co.kr/modules/moniwiki ··· read_api

mutex와 조건변수
: http://www.joinc.co.kr/modules/moniwiki ··· EC%88%98
"UNIX/Linux C" 카테고리의 다른 글
Posted by webdizen

Trackback URL : http://www.webdizen.net/blog/trackback/2920