반응형


스콧 마이어스 

참. 주옥같은 한줄 한줄이 들어있는 책이죠.
GOF 디자인 패턴과 함께 손에 놓을 수가 없네요. 왠만한 소설책보다 재미있다는...ㅋ
2판은 후배가 본다고 해서, 서점에 들려서 3판을 주문해서 다시 보게 되었답니다. ㅎ


[Effective C++]

복사생성자와 대입연산자 처리 - 만약 내가 만들려는 class가 만약 복사가 필요없다면, 원천 차단 하라.!
     - 항목 6: 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해버리자!
        복사 생성자와 대입 연산자 private으로 선언하기! 

복사 생성자와 대입 연산자 예기가 나오니 몇달전에 겪었던 한 사건이 생각나네요. ㅎ

DLL 모듈을 작성하면서 만든 class가 있었는데, 우연찮게 다른 프로젝트에서 해당 class 와 같이 들어있던 define이 필요해서 include를 한적이 있었습니다. 다른 class들은 다 문제가 없었는데 유독 한 class에서 link error가 발생하더군요.
__declspec(dllexport) 로 선언되어있는 것이 문제긴 했는데, 다른 class들은 다 문제가 없는데 왜?????? 이 class만 문제가 되는가로 고민을 하게 되었습니다. 

그런데 원인은 알겠는데, 그렇다면 똑같이 선언된 다른 class들은 왜 문제가 없었는가? 하는것이었죠.
이걸로 한 2시간 고민하다가 해더파일의 class코드를 다 지우고, 문제가 발생하지 않은 녀석과 문제가 발생한 녀석 2개를 놓고, 한줄 한줄 copy하면서 비교해 봤습니다.

결국 두 클래스간의 차이는 복사연산자와 대입연산자가 금지되어있는가 아닌가 의 차이더군요.
대입연산자 (operator =)를 금지 시켰더니 문제가 해결되더군요.



변수 초기화
C와 C++의 차이.
우선 C는 기본적으로 변수 초기화가 안되죠. C++은 객체이기 때문에 객체를 생성하는 시점에 초기화가 기본적으로 되죠.(constructor 불리니까 말이죠).

C의 경우 물론 다른 정책을 가지고 있습니다. C++ 예기에 주로 관심이 있으니 넘어가도록 하겠습니다.

항목 4: 객체를 사용하기 전에 반드시 그 객체를 초기화 하자.
를 살펴보면 이런 대목이 있습니다.
"C++의 객체 초기화가 중구난방인것은 아니다. 언제 초기화가 보장되며 언제 그렇지 않은지에 대한 규칙이 명확히 준비되어있다. 그런데 복잡하다."
뭐 대충 이런 구문인데,과연 무엇이 복잡한가?
 
정적객체(static object) 중에서 비지역적 정적 객체와  지역 정적 객체가 있는데, 함수내의 static object만 지역 정적 객체이고 나머지는 비 지역적 정적객체입니다.  
비 지역적 정적 객체는 compiler가 생성시점을 결정하기 때문에 생성 순서나 초기화 순서를 개발자가 결정할수 없게 됩니다. 
때문에 비지역적 정적객체를 지역 정적객체로 바꿔서 사용하는 것이 일반적입니다.

CWindowManager * CWindowManager::GetInstance()
{
static CWindowManager windowManager;
return &windowManager;
}

또는
CWindowManager * CWinMgr(void)
{
static CWindowManager windowManager;
return &windowManager;
}

이런식이죠. 말은 어렵지만 코드는 단순합니다. ㅎ
코딩할때는 이렇게 사용하겠죠.

CWindowManager * pWinMgr = CWinMgr();
또는
CWindowManager * pWinMgr = CWindowManager::GetInstance();
 
pWinMgr->GetWindow(x,y);



컴파일러의 종류나 구현에 따라 다르게 동작하는 부분
 - 항목17: new로 생성한 객체를 스마트 포인터로 저장하는 코드는 별도의 한 문장으로 하자.

책에 있는 예제로 가보면,
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw,int priority);
     :
processWidget(std::tr1::shared_ptr<Widget>(new Widget) , priority());

의 예제 코드를 보면, resource leak이 발생할 가능성이 있답니다.
컴파일러가 processWidget 호출 코드를 만들기 전에 우선 이 함수의 매개변수로 넘겨지는 인자를 확합니다.
이때 첫번째 인자인 std::tr1::shared_ptr<Widget>(new Widget)는 2부분으로 나뉩니다.

- new Widget
- tr1::shared_ptr -> 생성자 호출부분

때문에 processWidget을 호출하기 전에, 컴파일러는 
priority()를 호출하는 부분,
new Widget 을 실행하는 부분.
tr1::shared_ptr 생성자를 호출하는 부분.

위와 같이 3부분으로 나뉜다고 한다면, 컴파일러에 따라, priority() -> new Widget -> tr1::shared_ptr 생성자 호출의 순서가 될 수도 있고, new Widget -> priority() -> tr1::shared_ptr 생성자 호출 이 순서가 될 수도있습니다.

두번째 경우 priority() 에서 exception이 발생한다면, new Widget으로 생성한 포인터는 유실될 가능성이 있습니다.
자원 유실을 막기 위해 shared pointer 를 사용했는데, 예측할 수 없는 위치에서 자원이 유실된 케이스겠죠.

이럴 바에야 
std::tr1::shared_ptr<Widget>pw(new Widget);
processWidget(pw,priority());
를 따로 써서 고민꺼리를 미리 예방하는게 좋겠다.!!! 는 것...


강력한 보장: 함수가 호출되고 내부에서 error 처리 루틴에 의해 fail을 return할때는 함수가 호출된적이 없었던 것 처럼 깔끔하게 프로그램 상태가 돌아가게 한다.는... 
- 항목 29:예외 안정성이 확보되는 그날 위해 싸우고 또 싸우자!

Hiding: ????
-항목 33:상속된 이름을 숨기는 일은 피하자
음.. 아마 대부분의 경우 의도적으로 상속된 멤버의 이름을 숨기는 일은 별로 없을것이다.
우연찮게, 또는 실수에 의해서 HIDING되는 경우가 많다. 
- 이런 경우 정말 c++에 대해서 잘 알지 못하면, 문제의 원인을 알아내기 힘들다. 또 잘알고 있더라도 찾아내기 힘들다.
  다행히도 이럴때 compiler가 warning을 내주긴 한다.
다음 예제를 보자!(Effective C++에 나온 예를 노가다 타이핑 했습니다. ㅡㅡ;;)

class Base{
private:
   int x;
public:
  virtual void mf1()=0;
  virtual void mf1(int);
  virtual void mf2();
  virtual void mf3();
  virtual void mf3(double);
  ...
};

class Derived: public Base
{
public:
  virtual void mf1();
  void mf3();
  void mf4();
};

void test_main(void)
{
Derived d;
int x;
...
d.mf1(); //좋습니다. Derived::mf1을 호출합니다.
d.mf1(x);//에러. Derived::mf1이 Base::mf1을 가립니다.
d.mf2();//좋습니다. Derived::mf2을 호출합니다.
d.mf3();//좋습니다. Derived::mf3을 호출합니다.
d.mf3(x);//에러. Derived::mf3이 Base::mf3을 가립니다.
}

어 ! 왜 에러지? 이런 의문을 품게 될것입니다. "상속받은 서브클레스가 슈퍼클레스의 멤버를 호출하는데 왜 에러지?" 라고 말이죠.!!
하이딩에 대해서 모르고 있기 때문입니다.

C++ 의 네이밍 규칙중에 하나인, 기본 클래스로부터 오버라이드 버전을 상속시키는 경우는 막겠다.
즉, 슈퍼클레스에 정의된 이름과 동일한 이름을 서브클레스에서 정의해서 사용하게 되면, 서브클래스 내에서는 슈퍼클레스의 이름을 모두 가리겠다(hiding)는 의미입니다.
"warning: Deriver::mf1() hides virtual Base::mf1()" 이런 워닝을 보이면서 말이죠.


[More Effective C++]

예외(Exception)

"예외 처리는 프로그램을 여러가지 면에서 흔들어 놓습니다." 그 증세는 심각하고 급진적이며 불편하기까지 합니다.
초기화 되지 않은 포인터를 사용하는 것이 위험해지고, 리소스 누수가 발생할 가능성의 가짓수도 늘어납니다.
우리가 원하는 대로 작동하는 생성자와 소멸자를 만들기가 보다 더 힘들어집니다.
예외처리 기능이 들어간 실행 파일과 라이브러리는 덩치도 늘어나고 속도도 느려집니다.

또 어떻게 해야 제대로 사용하는지 모릅니다.(예외가 발생했을때 어떻게 해야 적절하고 안정적으로 동작하게 하는 벙법에 대해서 의견일치가 아직도 이뤄지지 않았다는 것입니다.)

여기 내용을 찾아보시기 바랍니다. - EXCEPTIONHANDLING: 
A FALSE SENSE OF SECURITY 
byTom Cargill


setjump 와 longjump 는 exception 처리하는 루틴과 비슷하다. 차이라면, 스택을 거슬러 올라가면서 스택 내의 c++ 객체의 소멸자를 호출하지 않는 다는 것이다.


항목 9: 리소스 누수를 피하는 방법의 정공은 소멸자이다.

void processAdoptions(istream & dataSource)
{
while(dataSource)
{
ALA* pa = readALA(dataSource);
try{
pa->processAdoption(); // 예외를 던질 녀석
}
catch(...)
{
delete pa;
throw;
}
delete pa;
}
}

위와 같이 pa->processAdoption()에서 예외를 던질것을 예상하고 코드는 복잡하게 처리되기 마련이다.
이를 스마트 포인터를 써서 구현하게 되면 훨씬 간결한 코드가 나오게 된다.

void processAdoptions(istream &dataSource)
{
while(dataSource)
{
auto_ptr<ALA> pa( readALA(dataSource) );
pa->processAdoption(); // 예외를 던질 녀석
}
}

위 처럼 pa->processAdoption()가 예외를 던지더라도 while 구문을 빠져 나갈때 auto_ptr<ALA>의 소멸자가 불리면서 내부에서 ALA의 소멸자를 호출하기 때문에, 훨씬 간결해지고, 리소스 관리가 편해지게 됩니다.


항목 10: 생성자에서는 리소스 누수가 일어나지 않게 하자

C++은 생성과정이 완료된 객체에 대해서만 안전하게 소멸 시킵니다. 만약 생성자 내부에서 exception이 발생하게 되면, 해당 객체의 소멸자는 호출되지 않습니다.


항목 11: 소멸자에서는 예외가 탈출하지 못하게 하자.
예외처리가 진행되고 있는 동안 다른 예외 때문에 소멸자를 떠나게 되면, C++은 terminate란 함수를 호출하게 되어있습니다.
이 함수는 프로그램의 실행을 끝장내기 때문에, 것도 아주 칼같이 끝내 버리기 때문에,지역 객체조차도 소멸되지 않습니다.

Session::~Session()
{
try{
logDestruction(this);  // 예외를 던지는 녀석
}
catch(...){ }    //expction으로 소멸자를 빠져 나가는 것을 잡고있는 녀석
}

위의 try catch구문만으로도 아주 훌륭한 소멸자 예외처리가 되는 것입니다.

항목 15: 예외처리에 드는 비용에 대해 정확히 파악하자

통상적인 함수 복귀(return)과 비교할때 , 예외 발생(throw)에 의한 함수 복귀 속도는 10의 세 제곱배(1000배) 만큼 느리다고 합니다.

예외 비용을 최소화 하려면, 가능하다면 예외 기능을 지원하지 않도록 컴파일하십시오.
try 블록과 예외 지정은 꼭 필요한 부분에만 사용합니다. 예외를 발생시키는 일도 진짜 예외적인 상황이라고 판단될 때에만 해야 합니다.

80-20 법칙 : 예외 자체는 사실 프로그램의 동작상 20에 해당합니다. 프로그램의 성능에 영향을 크게 미치는 것이 아닙니다.
단지 예외 발생이 자주 일어난다면, 그때는 예외를 사용할 것인지 심각하게 고려해봐야 합니다.




오 남용.



반응형

많은 저명한 학자들이나 연구가들 그리고 리더들이 "미래는 이럴것이다." 라고 자신들이 생각하는 미래에 대한 자신의 예측을 설명(?)을 하곤 합니다. 하지만 실제로는 미래를 예측하고 준비한다는 것은 불가능하고 또 도박에 가까운 것이죠.
그런데 그것이 실제로 일어났습니다. ( 어디서 본 유행어였는데 ㅎㅎ)

이런 이유는 뭘까요?
한가지 예를 들면 이런것이겠죠. 
아무도 하이닉스에 별 관심이 없었는데, 저는 "하이닉스 주가가 내일 오를것이다." 라고 생각하고 있으면 오를까요?   절대 있을수 없는 일이죠. 하지만, 제가 "하이닉스 주식 내일 오를꺼에요." 온 동내에 예기하고 다니고, 인터넷 블로그, 트위터, 페이스북에 도배를 하고 뉴스에 내보내면 어떨까요?. 많은 사람들이 관심을 갖겠죠?. 또 오를 가능성이 아무것도 안했을때 보다 매우 높아집니다. 
그렇다면, 유명 펀드 매니져가 뉴스나 주식 시장을 설명하는 TV프로에 나와서 "현재 하이닉스 주가가 저평가 되어있고 최근 많이 가격이 낮아져서 주가가 바닥인 상태입니다. 더군다나 이번 상반기 실적이 매우 좋으니 어쩌고 저쩌고, 하이닉스 주가는 내일 부터 오를 것입니다." 라고 한다면 진짜 오을 가능성은 제가 예기 했을때와는 비교도 안되게 오를 가능성이 높아질 것입니다.



이럿듯이 이건 미래를 예측한것이 아니라 미래를 제시한것입니다. 
어느 한 분야에서 전문가라 불리는 사람의 한마디에 의해 미래가 그사람이 제시한대로 변할 가능성이 생기는 것입니다.
그것은 그 전문가에 의해서가 아니라 그의 의견에 동조하는 능력있는 사람들은 그가 제시한 미래를 만들기 위해 노력하여 기반 기술들이 완성되어 가면서 제시된 미래에 가깝게 되는 것이죠. 

우리는 미래에 대해 예기하는 여러사람들의 이야기에 귀를 기울이고, 적극적으로 사고할 필요가 있습니다. 또 자신의 생각에 대해서도 목소리를 내어 이야기 할 필요가 있습니다. 그래야 자신이 원하는, 기대하는 미래가 만들어지기 때문입니다. 

이는 비단 기술적인 분야에만 해당하는 것이 아니라, 모든 사회 전반에 대해서도 마찬가지입니다.

그런 의미에서 저도 한가지 예언(?)을 ㅎㅎ 
정직한 사람이 인정받는 사회, 행복한 가족, 자신감있는 내모습, 예쁜 여자친구 이 모든 것이 조만간 다가올것입니다. ㅎ


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

모바일의 다음 시대는 ?

김종훈 벨연구소 사장님이 예견하는 미래의 모바일 환경에 대한 기사가 나와있어서 스크랩했습니다.

휴대폰이 없는 시대, 동영상의 시대. 정말 올것 같군요. ㅎ


김종훈 벨연구소 사장 "3~5년내 휴대폰 없이 통화한다"
"앞으로 모든 사물에 인터넷이 들어가면서 휴대폰 없이 통화하는 시대가 옵니다. 영화 스타트랙이 현실화하는 것이죠." 

세계적인 IT 연구개발센터인 벨연구소(Bell Lab)를 이끌고 있는 김종훈 사장(50)이 밝힌 3~5년 후 미래상이다. 

인터넷이 냉장고, 세탁기 등 가전제품에 들어가고 자동차에서도 사용할 수 있게 되면서 휴대폰이 없어도 개인 인증만으로 언제 어디서나 인터넷전화를 사용할 수 있게 된다는 것이다. 

이같이 하드웨어(스마트폰, 태블릿PC 등)가 없이도 이용자가 소셜네트워크서비스(SNS), 전화 통화, 이메일 등의 디지털 서비스를 자유롭게 사용(디바이스 프리)할 수 있도록 제공하는 사업자가 향후 비즈니스 전쟁에서 승리할 것이란 전망이다. 

김 사장은 최근 매일경제와 단독 인터뷰하면서 `포스트 애플ㆍ구글` 시대를 위해 한국 IT 기업들이 나아가야 할 방향에 대해 밝혔다. 김 사장은 "클라우드 컴퓨팅으로 인해 영화, 음악, 뉴스 등 콘텐츠가 가상의 공간에서 처리되는 시대(네트워크 및 소프트웨어의 가상화)가 왔다"며 "앞으로는 하드웨어도 가상화되는 시대가 올 것으로 본다"고 강조했다. 

김 사장은 2005년부터 7년째 알카텔루슨트 벨연구소를 이끌고 있다. 

벨연구소는 그레이엄 벨의 이름을 따 1925년 설립됐으며 노벨 물리학상 수상자를 포함해 13명의 노벨상 수상자를 배출한 `미국의 자존심`으로 불리는 세계적 연구기관이다. 

김 사장은 앞으로 미래 IT산업은 `동영상 콘텐츠`에 의해 좌우될 것이라고 예측했다. 김 사장은 "모든 콘텐츠가 동영상이다(Video is Everything)"라고 압축해 표현했을 정도다. 이 때문에 벨연구소에서는 비디오 사용량 증가로 인한 트래픽 증가에 대처할 수 있도록 네트워크를 효율적으로 확장하는 연구를 진행하고 있다고 소개했다. 

김 사장은 "지금 젊은이들은 유튜브 동영상으로 대화하고 학습하고 숙제도 한다. 동영상은 인간의 활동에 영향을 줄 것"이라며 "휴대폰으로 음성통화나 문자메시지를 주로 쓰는 세대는 이해 못하겠지만 앞으로 동영상으로 대화하는 시대도 온다"고 강조했다. 이른바 `유튜브 제너레이션(YoutubeGeneration)`이 등장하고 있다는 뜻이다. 

김 사장은 "어른들은 음성통화만 중요했다. 문자가 등장하면서 지금 세대는 음성보다 문자를 더 쓴다. 이제 비디오가 그 시작이다. 동영상을 중심으로 써온 세대는 지금 세대를 이해하지 못할 것"이라고 예측하기도 했다. 

◆ 유튜브 제너레이션 등장 

= 동영상 폭발을 경험하게 될 것이기 때문에 한국 IT 기업들도 음성과 데이터는 물론 동영상 시대를 미리 준비해야 한다고 강조하기도 했다. 

김종훈 벨연구소 사장은 IT 산업의 시작은 통신에서 출발했고 공급 중심의 경제(Supply Economy) 였다고 설명했다. 

애플, 구글 이후 수요(Demand) 중심 경제로 모든 것이 옮겨졌기 때문에 서비스와 네트워크가 중요해지고 있다는 것이다. 김 사장은 "철학은 간단하다. 하드웨어, 소프트웨어 그 다음은 서비스"라며 `포스트 애플ㆍ구글`은 서비스를 혁신할 수 있는 기업이 만들 수 있을 것으로 내다봤다. 

김 사장은 "애플은 소프트웨어를 잘 하려면 훌륭한 하드웨어를 가지고 있어야 한다는 점을 증명했다. 아이폰은 하드웨어가 아니라 프로그램으로 작동하는 스마트폰"이라며 "앞으로는 서비스를 이용자들에게 가장 빨리 구현할 수 있어야 한다"고 말했다. 

김 사장은 또 벨연구소가 지난해 출범시킨 글로벌 컨소시엄인 `그린 터치(Green Touch)`를 성공시키기 위해 잠을 못 이룰 정도로 고민이 많다고 털어놨다. 

그린 터치는 통신 네트워크의 에너지 효율성을 현재보다 1000배 이상 향상시킬 수 있는 기술을 개발하는 것으로 현재 사용되는 하루 전력 소비량으로 3년간 운용이 가능해지는 수준을 의미한다. 

벨연구소와 한국 삼성종합기술원, 미국의 MIT와 스탠퍼드대학 등이 컨소시엄을 함께 구성해 참여하고 있다. 

김 사장은 "그린 터치 컨소시엄은 (벨연구소를 이끌고 있는) 내 자신의 모험이 아닌 벨연구소의 오랜 명성과 평판을 거는 위험이 따르는 프로젝트"라며 "새로운 것을 발명하기 위해선 해야 할 과제가 많다"고 말했다. 

■ < 용어설명 > 

유튜브 제너레이션(Youtube Generation) : 유튜브 등 동영상 콘텐츠를 통해 학습하고 대화할 줄 아는 세대. 

그린터치(Green Touch) : 벨연구소가 주축이 돼 만든 글로벌 컨소시엄. 통신 네트워크의 에너지 효율성을 현재보다 1000배 이상 향상시킬 수 있는 기술을 개발하는 것이 목표다. 
반응형

html 5를 공부하기 좋은 sample 자료네요..
이것 저것 해볼것도 많고 ㅎ


반응형
불확실성과 화해하는 프로젝트 추정과 계획(마이크 콘) 이라는 책의 13번째 파트 에 해당하는 내용입니다.


릴리즈 계획이 뭐길래? 왜?  <-- 요거 제목으로는 어울리지 않죠? ㅎㅎ 릴리즈에 대해서 다들 알고 있을 건데.. 왜라니.. 쩝. ㅋ 

릴리즈 계획 과정은 이터레이션보다 더 긴 기간을 커버하는 아주 높은 수준의 계획을 생성하는 과정이다.
릴리즈를 내놓는 데는 통상 3개월에서 6개월의 기간이 소요되며, 이터레이션 길이가 얼마냐에 따라서 3회에서 12회 이상의 이터레이션이 필요하다.

첫 번째로 릴리스 계획은 제품 책임자와 팀원들로 하여금 릴리스 가능한 제품이 나오려면 얼마나 많은 작업이, 그리고 얼마나 오랜 시간이 필요할지 결정할 수 있도록 해준다. 제품이 더 빨리 릴리스 될수록(그리고 릴리스 시점에 제품의 질이 좋으면 좋을수록), 회사는 프로젝트로부터 더 빨리 수익을 거둘 수 있게 된다.
  -- 이런 내용들이 관리자들이 좋아하는 내용이죠? ^^;;

두 번째로 릴리스 계획을 통해 얼마만큼의 기간 동안, 어떤 기능이 개발될 것인지를 예측할 수 있다. 많은 회사들이 이 정보를 사용해 다른 전략적 계획들을 수립한다.

세 번째로 릴리스 계획은 개발팀이 어디로 가야할지를 보여주는 이정표 구실을 한다. 릴리스라는 개념이 없다면, 개발팀은 한 이터레이션에서 다음 이터레이션으로 끝없이 옮겨 다니게 될 것이다. 릴리스 계획 덕에 이터레이션들은 하나의 만족스로운 총체로 결합될 수가 있다. 이는 굳이 애자일 프로세스가 아니더라도, 반복적인 프로세스라면 기본적으로 고려해야할 사항이다. 
이런 계획이 없다면 프로젝트가 목적지 없이 표류할 가능 성이 생기고, 또 완료를 하더라도 기대했던 방향이 아닐 수도 있다.
그러므로 릴리스 계획이 필수적이며 이로인해서 프로젝트가 잘못된 길로 가는 걸 막아줄 수 있다.... 랍니다. ㅋ

릴리즈 계획
릴리스 계획 과정에서는 얼마만큼의 작업이 완료 될것인가를 결정해야 한다. 
날짜를 정해놓고 얼마만큼의 일을 할 수 있을지 생각해보는 것이 프로젝트의 시작일 수도 있고,사용자 스토리들을 만들어 둔 다음 그것들을 구현하는데 얼마나 오래 걸릴지를 생각해 보는 것으로 프로젝트를 시작할 수도 있다.
개발팀이 최초의 답을 얻고 나면 그 답은 해당 프로젝트에 대한 조직의 목표에 비추어 평가된다.
결국 제품을 개발하고 나면 기대한 만큼의 돈을 벌 수 있을 것인가? 아니면 돈을 절약할 수 있을 것인가? 시장에 성공적으로 진입할 수 있을 것인가? 만일 아니라면, 프로젝트 수행 기간을 더 늘리거나 줄여서 목표를 성취할 수 있을지도 모른다.

릴리스를 내놓기 위해 얼마나 많은 일을 해야 할지, 그리고 어떤 사용자 스토리들을 포함할지를 결정하는 것은 아주 직관적인 프로세스이다. 계획된 이터레이션 숫자에 개발팀의 예상 속도나 알려진 속도를 곱하면 얼마나 많은 일이 필요한지를 결정할 수 있다.

그 수치에 맞춰서 사용자 스토리의 수를 결정하면 된다. 가령 6개월 뒤에 새로운 제품을 내놓아야 한다고 해보자, 두 주짜리 이터레이션을 사용하기로 했다면, 릴리스 시점까지 13번의 이터레이션을 돌아야 할것이다.
개발팀의 속도가 스토리 점수나 이상적인 작업일 기준으로 20이라면, 전체 프로젝트의 규모는 13x20= 260점이 될 것이다. 제품 책임자와 개발팀은 그 점수를 놓고 모든 스토리들을 토론 해 본 다음 그 우선순위를 정해 260이 넘지 않는 한도 내에서 가장 가치 있다고 여겨지는 스토리들을 구현하기 위해 노력할것이다. 릴리스 계획은 보통 프로젝트 기간 동안 개발될 사용자 스토리의 목록 형태로 문서화된다.

여기쯤 읽을때 이런 생각을 해봤습니다.
위에서 개발팀의 속도를 20이라는 결과를 얻기 위해서는 무엇이 필요할까? 라는 것입니다.
이론상으로는 너무나도 쉬운 답을 얻을 수 있습니다. 실제로 팀원들과 스토리 보드 작성과 이터레이션 작성을 한 내용을 가지고 평가를 하면 됩니다.

하지만 제가 진행해본 결과( 전문 프로젝트 메니져가 아니라 너무 미숙 했을 수도 있지만 ) 정확한 추정이 힘들다는 것을 알게 되었습니다.

1. 전체 제품에 들어가야 하는 정확한 스토리를 뽑아 내기가 힘들다는것.
2. 프로젝트를 진행하면서 튀어나오는 이슈들이 발생할때 이것들이 스토리로 들어가기에 문제가 있는 경우들.
3. 스토리나 이터레이션 수행에 거짓이 있을때.( 보고를 위해 빈약한 부분을 채워 넣거나, 혹은 빼먹거나 하는 것들).

1번과 2번은 사실 프로젝트 수행 미숙에 해당하는 내용으로 볼 수도 있습니다.
3번은 너무나도 취약한 부분입니다. 부서내에서 업무 수행 보고를 부풀려 해야 하거나 할 때가 있습니다.
이런 문제들이 개입되다보면, 결국 이 개발팀의 속도 추정치가 불확실 해지고, 이는 전체 프로젝트 스케쥴에 큰 영향을 미치게죠.

그렇다 보니 개발 스케쥴을 신뢰 할 수 없게 되고, 관리자는 임의대로 스케쥴을 조정하게 되는 악 순환이 반복될 가능성이 있습니다.

당연히 개발팀장 역시 자신이 보고한 개발 스케쥴에 대해서도 자신감이 없으니 강력한 주장(?)은 더더욱 힘들게 됩니다.
저 역시 이런 오류들을 격다보니, 정확한 팀의 속도를 추정은 불가능 하더군요.


그래서 향후 새로운 프로젝트를 진행할 시에는 3번과 같은 경우는 없도록 해야겠다는 걸 뼈저리게 느꼈답니다.


릴리스 계획 과정 동안 어떤 개발자가 어떤 사용자 스토리나 작업을 수행할 것인지 명시하는 계획을 만들거나, 이터레이션 동안 어떤 작업이 어떤 순서대로 이뤄져야 하는지를 명시하는 계획을 만들지는 않는다. 릴리스 계획 과정동안 그 정도로 자세한 계획을 만드는 것은 위험한 일이다. 누가 무슨 일을 어떤 순서대로 할 것이냐 하는 문제는 그 작업을 할 사람이 결정하는 것이 좋고, 가능한 한 뒤로 미뤄두는 것이 좋다. 릴리스 계획에 명시되는 것은 사용자에게 전달될 기능의 명세인 사용자 스토리이지 실제로 수행될 개별적 작업들이 아니다. 
그런 것들을 릴리스 계획에 포함시키는 것은 너무 이른 일이다.
사용자 스토리들 가운데에는 개별적인 엔지니어링 작업들로 분할하기에는 충분히 이해되지 않은 스토리들도 있을 수도 있다. 
14장[이터레이션 계획 과정] 에서도 살펴보겠지만, 개발팀은 결국 릴리스 계획에 포함된 사용자 스토리들을 개별적인 작업들로 분할하게 된다. 하지만 그러한 분할 작업은 해당 스토리가 속한 이터레이션을 시작할 때까지는 하지 않는다.
 핵심이 될만한 키워드라서 강조 했습니다.  사실 프로젝트 계획을 세우다보면 너무 세부 사항까지 들어가게 되는데 이를 막고자 하는 예기 인듯 합니다.

만일 프로젝트나 조직 그리고 작업 환경이 허락한다면 릴리스 계획안에 부가적인 정보들을 당연히 포함시킬 수 있다. 일례로, 계획 이면에 깔려있는 어떤 핵심적인 가정들을 릴리스 계획에 명시해 둘 수도 있다. 그런 가정들로는 누가 개발팀에 포함되어야 하고, 이터레이션 길이는 얼마나 되어야 하고, 첫번째 이터레이션이 언제부터는 시작되어야 하고, 마지막 이터레이션은 언제까지 끝나야 한다는 등의 가정이 있을 수 있겠다. 제 21장 [계획과 소통]에서는 릴리스 계획에 대한 의견을 서로 나눌때 포함 시키면 좋은 부가적인 정보들에 대해서 설명한다.


만족 조건 결정 -> 사용자 스토리 추정 -> 이터레이션 길이 결정           -> 스토리 선정 및 릴리스 날짜 결정  
             ↑                                            속도 추정                                                          |
             |                                            사용자 스토리들 간의 우선순위 결정                       |
             +-----------------------------------------------------------------------------+


만족 조건 결정
릴리스 계획과정을 시작하기 전에 프로젝트의 성패 여부를 판정할 기준을 아는 것이 중요하다.
대부분의 프로젝트의 경우 가장 결정적인 기준은 절감되거나 창출되는 돈의 양이다. 프로젝트가 이런 재정적 목표를 만족할 것인지를 판단하기 위해 대부분의 프로젝트들은 일정과 범위 그리고 자원이라는 3대 기준을 사용한다. 그것은 제품 책임자가 정하는 만족 조건들이라는 것이 일정과 봄위 그리고 자원에 관한 목표들로 정의된다는 것을 의미한다.

제품 책임자는 릴리스 계획 과정에서 행해지는 미팅 때마다 이들 각각에 대한 목표들을 꺼내 보여줄 것이다. 가령 제품 책임자는 네 개의 테마에 대해(스토리 점수 기준으로 200점에 달하는)인원 추가 없이 3개월 안에 개발 완료를 바랄수도 있다. 각자의 기준들에 대해 전부 목표를 정해둘 수도 있지만, 보통은 하나의 기준이 지배적으로 작용한다. 즉, 일반적인 프로젝트들은 날짜를 맞추는 것을 목표로 하거나 아니면 기능을 맞추는 것을 목표로 한다는 것이다. 일정 중심적인 프로젝트는 특정한 날짜까지는 릴리스가 나와야 하는 프로젝트이며, 어떤 기능이 포함되어야 하느냐는 그렇게 중요하지 않다.
기능 중심적인 프로젝트는 가능한한 빨리 릴리스를 내놓고자 하는 프로젝트이긴 하지만, 어떤 기능이 제품에 포함되느냐가 보다 더 중요한 프로젝트이다.

만족 조건 결정이라는 것이 책의 내용으로도 잘 이해가 안되는 부분이군요. 과연 만족할만한 조건이란 것이 위에서 나열한 것들로 결정할 수 있는가? 그럼 일정이 중요하다고 한다면 만족할만한 조건이란 무엇인가? 예가 없으니 너무 막연하군요.


스토리와 릴리즈 날짜 선정

이 시점에는 개발팀의 이터레이션 당 속도를 알고 있는 상태일 것이고, 얼마나 많은 이터레이션이 필요할지에 대한 기본적인 가정은 되어 있는 상태일 것이다. 그렇다면 릴리스가 만족 조건들을 충족하도록 계획될 수 있는지 살펴볼 순서이다.
프로젝트가 기능 중심적이라면 모든 필요 기능들의 추정치를 합한 다음 그 값을 예상 속도로 나눈다. 그렇게 하면 필요한 기능 구현을 완료하는 데 필요한 이터레이션의 횟수를 구할 수 있다.
프로젝트가 일정 중심적이라면 달력을 보고 이터레이션의 횟수를 정한다. 이터레이션의 횟수를 추정된 속도로 곱하면 얼마나 많은 스토리 점수를 릴리스 안에 넣을 수 있을지 알 수 있다. 그리고 그 만큼의 점수나 이상적 작업일을 사용자 스토리의 우선순위 리스트에 적용하여 얼마나 많은 기능을 지정된 시간 안에 구현할 수 있을지를 알아낸다.
그 다음 물어야 할 질문은 릴리스 계획이 얼마나 상세해야 하는가이다.
어떤 팀들은 각 이터레이션 안에 무엇을 개발해야 하는지가 잘 드러나는 릴리스 계획 쪽을 선호한다. 어떤 팀들은 다음 릴리스까지 어떤 기능들을 개발해야 하는지 정도만 간단히 결정하는 쪽을 선호한다. 각 이터레이션 안에서 무엇을 해야 하는가는 나중에 결정하겠다는 것이다. 이 중 어떤 방식을 택할것이냐 하는 것을 릴리스 계획 과정에서 토의하고 결정하여야 한다.

각각의 접근 법에는 나름대로의 장단점이 있다. 분명한 것은, 특정한 기능을 특정한 이터레이션에 배정하는 작업은 더 많은 시간을 필요로 한다는 것인다. 하지만 그정도로 상세하게 작업하게 되면 여러 팀들이 협력하여 일을 진행해야 할 경우 유리하다. 한편,작업을 특정한 이터레이션에 할당하는 과정을 생략하면 상세함은 떨어질지라도 시간은 덜 잡아먹는다. 더욱이 어떤 작업을 어떤 이터레시션에 배정할 것인지를 미리 결정하더라도 이터레이션이 실제로 시작되는 시점보다는 적은 지식을 가진 상태에서 하게되는데, 프로젝트 진행과정에서 더 많은 지식을 축적하다 보면 계획은 변경되게 마련이다. 그사실에 비추어 본다면, 작업을 특정한 이터레이션에 배정하기 위해 시간과 노력을 들이는 것은 바람직하지 않다. 물론 프로젝트에 따라서는 그런 사전 작업을 할만한 가치가 있는 경우도 있다. 
필자가 적절한 타협점이라고 생각하는 방법은 , 첫 번째 이터레이션부터 세 번째 이터레이션까지는 각 이터레이션에서 어떤 작업을 수행할지를 미리 정하고, 릴리스 계획의 나머지 부분에 대해서는 하지 않는 것이다. 맨 처음 이터레이션에 작업을 할당해 두는 것은 당연히 의미가 있다. 이터레이션이 곧바로 수행될 것이라면 더더욱 그렇다.
전형적인 릴리스 계획 과정 동안에는 아주 많은 타협적인 질문들과 가정적인 질문들이 제기된다. 그러므로 다음 릴리스와 그 이터레이션동안 무엇을 넣고 무엇을 뺄지를 능숙하게 처리하기 위한 손쉬운 방법이 필요하다. 가장 좋은 방법은 모든 사람들이 배석한다는 가정하에, 사용자 스토리나 기능이 적힌 3x5 인치짜리 카드나 포스트잇 카드를 놓고 작업하는 것이다. 소프트웨어와는 달리 카드는 실체적이며 쉽게 공유될 수 있다.
릴리스를 계획하기 위해서 제품 책임자는 그가 생각하기에 제일 우선순위가 높아서 첫 번째 이터레이션에 들어가야 한다고 생각되는 항목들을 고른다. 카드는 어떤 스토리가 어떤 이터레이션에 들어가야 되는지 명확하게 드러나도록 배열된다.

그림.이터레이션에 들어갈 작업들을 열로 나누어 배열하기.
여기 그림이 이터레이션이 가로로 배열되고, 세로로 스토리들이 배열된 그림이 들어옴


릴리스 계획의 갱신

릴리스 계획을 만들어 두고 어딘가에 처박아 둔 다음 건드리지 않으면 곤란하다. 릴리스 계획은 수시로 점검되어야 하며 주기적으로 갱신되어야 한다. 개발팀의 속도가 일정하게 유지되고 이터레이션 계획 과정에서 뭔가 깜짝 놀랄 만한 사건이 발생하지 않으면 네 주나 여섯 주 정도는 릴리스 계획을 갱신하지 않고 놔두어도 괜찮지 않을까 생각할 수도 있다. 하지만 많은 프로젝트들은 이터레이션이 끝날 때마다 릴리스 계획을 재점검한다는 규칙을 지켜서 좋은 성과를 내고 있다.

사용자 스토리 선정
이번 릴리스에 약 48점 만큼의 스토리를 둘 수 있는 상황에서,
팀원들은 제품 책임자가 가장 낮은 우선순위를 준 "시스템 관리자는 사용자 그룹과 권한을 설정할 수 있다."는 스토리가 시스템에 반드시 필요한 스토리라는 사실을 지적하였다. 이 스토리는 3점짜리이다. 이스토리를 릴리스에 포함시키면 스토리 점수 총합은 49점이 되어 48점을 넘어버린다. 
48점이라는 것도 역시 추정이기 때문에 49점을 그대로 수용할 수도 있지만, 49점을 수용하지 않기로 개발팀이 결정했다고 한다면,
1점만큼의 스토리를 릴리스에서 빼야 한다. 그래서 8점짜리 스토리를 빼버리기로 결정하였다. 이로서 41점이 되었는데 남은 자리에 3점짜리 스토리를 추가할 수도 있게 되었다.
하지만, 제품 책임자는 41점으로 줄어든 만큼 개발 팀이 일정을 1점 만큼 앞당길 수 있다면, 추후에 8점짜리 스토리를 제품일정에 추가할 수 있을 거라 생각하여 추가로 3점짜리 스토리를 포함하지 않고 놔두었다.

여기서 41점 짜리로 그대로 놔둔것은 매우 좋은 선택인것 같군요.
첫번째로, 48점 짜리 릴리스를 41점으로 줄이고 일정을 당길 수 있다면, 계획보다 많은 리소스를 줄일 수 있을것입니다.
두번째로, 41점 짜리 릴리스가 되어 7점이 아깝다고 생각 할 수도 있겠지만, 사실 새로 추가하려는 3점 짜리는 이미 우선순위에서 밀린 3점짜리 스토리라는 것입니다. 즉, 프로젝트상 8점 짜리 스토리보다 우선순위가 밀리는 것인데, 그다지 중요하지 않은 3점 짜리가 이번 릴리스에 들어감으로 해서 8점짜리 스토리가 들어간 다음 릴리즈 시기를 앞당길 수 있는 기회를 없애버릴 수 있기 때문이죠.


이 글을 읽기 전까지는 저도 스토리와 이터레이션과의 관계에만 몰두해서 프로젝트를 진행하면서 스토리와 이터레이션을 단지 TODO 관리 정도로 밖에 사용을 못했는데( 사실 이것도 굉장히 효과가 있었습니다.)
프로젝트 전반적인 흐름과 어떻게 매칭을 해나가야 하는지에 대해 좀더 깊게 생각해보게 되었습니다.




반응형



Linux 커널 드라이버 모형: 협업의 장점
  그레그 크로아 - 하트먼

리눅스 커널 드라이버 모형은 운영체제가 관리하는 모든 종류의 장치를 포괄하는 하나의 시스템 전역적 트리를 구축하기 위한것이다.

지난 수년 동안, 이를 위해 핵심 자료구조와 코드는 몇안되는 장치들을 다루는 하나의 아주 단순한 시스템으로 시작해서 현실 세계에서 처리할 필요가 있는 모든 종류의 장치를 제어하는 고도로 규모가변적인 시스템으로 발전해 왔다.

Linux 커널이 발전함에 따라 처리해야 하는 주변 장치들의 종류도 점점 더 늘어나게 되었는데, 그 과정에서 커널의 핵심부(Core)는 그렇게 다양한 장치 형식들을 좀 더 쉽게 관리할 수 있는 방식들을 받아들이며 진화해 왔다.

거의 모든 장치는 두 개의 구별되는 부분으로 구성된다. 하나는 운영체제와 장치의 연동 방식(PCI버스,SCSI버스,ISA버스,USB 버스 등을 통한)을 정의하는 물리적 부분이고 또 하나는 사용자가 장치를 적절히 사용할 수 있도록 운영체제가 장치를 사용자에게 제시하는 방법을 정의하는 가상의 부분(키보드, 마우스, 비디오,사운드 등)이다.
2.4 커널 릴리즈들에서는 장치의 물리적인 부분을 코드의 버스 관련 부분이 제어했다. 이 버스 코드는 다양한 과제들을 담당했는데, 각각의 개별 버스 코드는 다른 어떤 버스 코드와도 상호작용하지 않았다.

2001년에 모컬(Pat Mochel)은 Linux 커널의 전완 관리 문제를 해결하던 와중에 개별장치를 적절히 끄거나 켜기 위해서는 커널이 서로 다른 장치들 사이의 연결 관계를 알고 있어야 한다는 점을 깨닫게 되었다. 예를 들어 USB 컨트롤러를 끄기 위해서는 PCI 컨트롤러를 끄기전에 먼저 USB 디스크 드라이브를 꺼야 한다. 그래야 해당 장치에 자료를 적절히 저장할 수 있다. 이러한 문제를 해결하려면 시스템의 모든 장치의 연결관계와 연결 순서를 알 수 있는 하나의 트리가 있어야 한다.

다른 운영체제들은 장치의 이름 식별을 처리하는 작은 데이터베이스를 커널안에 두거나 한장치의 모든 가능한 고유 특성을 장치에 직접 접근하는데 사용할 수 있는 deffs 형식의 파일시스템에 익스포트함으로써 이런 문제를 해결했다. 그러나  Linux의 경우 커널에 데이터베이스를 두는 방식을 받아들일 수 없었다. 또한 Linux 의 devfs 파일시스템 구현에는 잘 알려진, 그리고 교정이 불가능한 race condition(경쟁조건)들이 여럿 존재하는 탓에, 거의 모든 Linux 배포판 들은 그 파일 시스템을 신뢰하지 않았다. devfs 해법은 또한 사용자에게 특정한 명명(naming)정책을 강요했다. 이를 장점으로 받아들인 사람들도 있었지만, 그 정책은 공표된 Linux 장치 명명 표준과 맞지 않을 뿐만 아니라 사용자가 자신이 원하는 다른 명명 정책을 사용하지 못하게 된다는 단점을 가지고 있다.

모컬과 나(그레그 크로아)는 우리의 문제가 Linux 커널 안에 있는 하나의 통합된 드라이버 및 장치 모형을 통해서 해결될 수 있음을 깨닫게 되었다. 그러한 통합 모형이 어떤 새로운 아이디어였던 것은 아니다. 그와 같은 통합 모형을 채용한 운영체제들은 과거에도 존재했다. 단지 ,Linux에서도 그것을 사용할 때가 되었던 것일 뿐이다. 
우리에게 필요한 것은 모든 장치의 트리를 생성하는데 사용할 수 있을 뿐만아니라 커널 외부의 사용자(userspace)프로그램도 임의의 장치에 대한 영속적인 이름을 사용자가 원하는 방식으로 처리할 수 있게 만드는 통합 모형이었다.


-- 소박한 시작
우리는 커널의 모든 장치에 대해 하나의 "기반" 클래스로 사용할 device라는 간단한 구조체를 만드는 일부터 시작했다.
초기에는 이 구조체는 다음과 같은 모습이었다.

struct device{
   struct list_head node;   /*sibling node */
   struct list_head children;
   struct device * parent;
   char name[DEVICE_NAME_SIZE];   /*서술적인 ASCII 문자열*/
   char bus_id[BUS_ID_SIZE];    /*부모 버스에서의 위치*/
   spinlock_t lock;                        /*서로다른 두 계층이 동시에 접근하지 못하도록 만드는 락*/
   atomic_t refcount;                     /* 장치가 적절한 시간동안 지속되게 만들기 위한 참조 회수(참조되고 있는 count)*/
   struct driver_dir_entry * dir; 
   struct device_driver * driver;       /*이 장치를 할당한 드라이버*/
   void * driver_data;                     /* 이장치의 전용 자료*/
   void * platform_data;                 /* 플렛폼 관련 자료(ex ACPI ,장치 BIOS 자료)*/
   u32 current_state;                      /*현재 작동상태. ASCI의 용어로 말하자면, 이것은 D0-D3 
                                                      D0는 완전히 켜졌음을 뜨하며, D3은 완전히 꺼졌음을 뜻한다.*/
   unsigned char * saved_state;    /*저장된 장치 상태*/
};

이 구조체를 생성해서 커널 드라이버 핵심부에 등록할 때마다 해당 장치와 그에 담긴 임의의 서로 다른 특성들을 대표하는 하나의 항목이 가상 파일 시스템에 만들어진다. 이를 통해서 시스템의 모든 장치가 사용자공간에 노출되며, 사용자 공간 프로그램은 이 가상 파일 시스템을 통해서 원하는 장치를 연결한다. 지금은 이러한 가상 파일시스템을 sysfs라고 부르는데, Linux가 깔린 컴퓨터의 /sys/devices 디렉터리에서 볼수 있다. 다음은 몇 가지 PCI와 USB장치들에 해당하는 부분이다.

/*원래는 여기에 linux의 device 내용들이 표시되어야 하는데. 타이핑 하기엔 너무 복잡해서 생략!!*/

struct usb_interface {
 struct usb_interface_descriptor * altsetting;
  int act_altsetting;                  /*활성화된 대체 설정*/
  int act_altsetting;                  /*대체 설정들의 개수*/
  int act_altsetting;                  /*할당된 메모리 총량*/
  struct usb_driver * driver;      /*드라이버*/
  struct device dev;                /*장치정보 관련 인터페이스*/
};

드라이버 핵심부는 struct device에 대한 포인터들을 주고받으면서 그 구조체에 있는 기본적인 ,즉 모든 장치에 공통인 필드들에 대한 작업을 수행한다.
포인터가 다양한 기능을 위한 버스 관련 코드로 넘겨질때에는 그것을 담고 있는 실제 구조체 형식으로 변환해야 한다. 이러한 변환을 처리하기 위해, 버스 관련 코드는 메모리 안에서의 포인터의 위치에 기초해서 포인터를 다시 원래의 구조체로 형변환 한다. 이를 담당하는 것이 다음의 메크로함수이다.
#define container_of(ptr,type,member) ({ \ 
          const typeof( (type*)0)->number)*__mptr = (ptr);\
          (type *)((char*)__mptr-offsetof(type,member) );})

예로 원래의 구조체의 struct device 멤버에 대한 포인터를 앞에 나온 struct usb_interface ㅍ포인터로 변환하는 코드를 보자.

int probe(struct device * d){
struct usb_interface * intf;
intf = container_of(d,strcut usb_interface , dev);
:
:
}

container_of 메크로와 같은 간단한 방법 덕분에 Linux 커널은 보통의 C구조체들을 아주 강력한 방식으로 상속하고 조작할 수 있게 되었다. 물론 그런 강력함은 개발자가 이들의 작동 방식을 제대로 알고 사용한다는 가정하에서의 이야기 이다.

애초에 struct device로 전달된 포인터가 실제로 struct usb_interface 형식인지를 실행시점에서 점검하지 않는다는 데에 의문을 표하는 독자도 있을 것이다.
전통적으로 , 이런 종류의 포인터 조작을 수행하는 시스템들은 대부분 기반 구조체에 조작 중인 포인터의 형식을 정의하는 하나의 필드를 두고 그것을 이용해서 프로그래머가 형식을 잘못 지정하는 실수를 검출한다. 또한 그러한 필드는 실행 시점에서 포인터의 형식을 동적으로 결정하고 그에 따라다른 일을 수행하는 코드를 작성할 때에도 요긴하게 쓰인다.

그러나 Linux 커널 개발자들은 그런 종류의 점검이나 형식 정의를 빼기로 결정했다. 그런 점검들의 경우 개발 초기의 기본적인 프로그래밍 실수를 잡아내는데 도움이 되기도 하지만, 나중에는 쉽게 잡아내기가 힘든 훨씬 더 미묘한 문제들로 이어질 수 있는 어떤 교묘한 편법들을 가능하게 만드는 수단으로도 악용 될 수도 있기 때문이다.

실행시점 형식 점검이 없기 때문에, 이런 포인터들을 다루는 개발자들은 자신이 다루고 넘겨주는 포인터의 형식을 반드시 명확하게 알고 있어야 한다. 물론 가끔은 자신이 보고있는 struct device의 구체적인 형식을 알아낼 수 있는 어떤 수단이 있으면 좋겠다는 생각도 들겠지만, 그런 생각은 문제를 적절히 디버깅하고 나면 사라진다.

형식 점검의 부재가 코드의 아름다움에 기여할 수 있을까? 이부분을 오년 이상 다뤄 온 입장에서 이야기하자먄, 답은"그렇다"이다.
이런 부재는 커널 내에서 종종 나타나는 손쉬운 편법을 방지하며, 모두가 자신의 로직을 아주 정확하게 짜도록 강제한다. 개발자들이 형식 점검에 의존하지 않도록 강제함으로써 버그를 미연에 방지할 수 있는 것이다.

여기서 나는 커널의 이 부분을 다루는 개발자들(공통 버스들을 위한 하위 시스템을 코딩하는 사람들)이 비교적 그 수가 적고 또 상당한 수준의 전문 지식 및 경험을 갖추었을 것으로 기대할 수 있다는 점을 지적해두고 싶다. 형식 점검이라는 안전장치를 두지 않는 것도 바로 이러한 이유에서이다.

기반 struct device구조체를 이런식으로 상속하는 방법 덕분에, 2.5 커널 개발 공정에서 우리는 서로 다른 모든 드라이버 하위 시스템들을 하나로 통합 할 수 있었다. 드라이버 시스템들은 이제 모두 공통의 핵심코드를 공유하며, 이에 의해 커널은 사용자에게 장치들이 어떻게 연결되어있는지를 제시할 수 있게 되었다.

또한 작은 사용자 공간 프로그램에서 영속적인 장치명명을 수행하는 udev 같은도구나 장치들의 트리를 훑으면서 장치들을 적절한 순서로 끄는 전원관리 모듈을 작성하는 것도 가능해 졌다.



-- 더욱 작은 조각들로 줄이기

초기의 드라이버 핵심부를 다시 만드는 도중에, 또 다른 커널 개발자인 바이로(Al Viro) 는 가상 파일시스템 계층에서의 객체 참조 횟수 집계와 관련된 몇 가지 문제점들을 고치고 있었다.

C언어로 작성된 다중 스레드 프로그램에서 구조체들에 생기는 주된 문제는, 한 구조체가 사용하는 메모리를 안전하게 해제 할 수 있는 시점을 결정하는 것이 아주 어렵다는 것이다. Linu 커널은 부주의한 사용자들 뿐만 아니라 동시에 실행되는 많은 수의 프로세서들도 적절히 처리해야 하는 하나의 대규모 다중 스레드 프로그램이다. 이 때문에 둘 이상의 스레드들이 사용하는 임의의 구조체에 대한 참조 횟수 관리가 필수적이다.

struct device 구조체도 그런 참조 횟수 관리 대상 중 하나였다. 이구조체에는 구조체를 해제해도 안전한지를 결정하는 데 쓰이는 다음과 같은 필드가 있다.
atomic_t refcount;
구조체가 처음 초기화 될 때 이 필드는 1로 설정된다. 이 구조체를 사용하고자 하는 코드는 우선 get_device 함수를 호출해서 참조 횟수를 증가해야 한다. 그 함수는 기존 참조 횟수가 유효한지 점검한 후 그것을 하나 증가한다.

static inline void get_device(struct device * dev)
{
BUG_ON(!atomic_read(&dev->refcount));
atomic_inc(&dev->refcount);
}

비슷하게 , 한 스레드가 구조체를 다시 사용했다면 put_device 함수를 호출해서 해당 참조 횟수를 하나 감소한다. 이 함수는 좀 더 복잡하다.


void put_device(struct device * dev)
{
if ( !atomic_dec_and_lock(&dev->refcount,&device_lock))
return;
:
:
/*
드라이버에게 구조체를 해제하게 한다.
우리가 장치를 할당 하지 않았을 가능성이 크므로,
지금이 드라이버가 장치를 해제할 기회이다...
*/
if (dev->driver && dev->driver->remove)
dev->driver->remove(dev,REMOVE_FREE_RESOURCES);

}

이 함수는 해당 구조체의 참조 횟수를 감소한다. 만일 그것이 0이면 구조체가 더 이상 쓰이지 않는 것이므로 드라이버에게 해당 구조체를 정리하게 한다( 그런용도로 시스템이 설정해 둔 함수를 호출해서).

바이로는 struct device 구조체의 통합과 서로 다른 모든 장치들을 보여주는 가상 파일시스템, 그것들이 서로 연결되는 방식, 그리고 자동적인 참조 집계 방싯을 맘에 들어 했다. 문제는 그의 가상 파일시스템 핵심부가 "장치"를 다루는 것이 아니며 이 객체들에 부착할 수 있는 "드라이버"도 없다는 데 있었다. 그래서 그는 코드를 조금 리팩토링해서 좀 더 단순하게 만들기로 결정했다.
바이로는 모컬을 설득해서 struct kobject라는 것을 만들게 했는데, 이 구조체는 struct device 구조체의 기본 속성들을 소유하되 더 작았고, 또 "드라이버"와 "장치" 관계는 가지지 않았다. 이 구조체의 필드들은 다음과 같다.

struct kobject {
char                          name[KOBJ_NAME_LEN];
atomic_t                    refcount;
struct list_head           entry;
struct kobject              *parent;
struct subsystem        *subsys;
struct dentry               *dentry;

};

이 구조체는 일종의 빈(empty)객체로, 참조 집계 및 객체들의 계통구조로의 삽입을 위한 아주 기본적인 기능만을 가지고 있다.
struct device 구조체는 struct kobject라는 이 더 작은 "기반" 구조체를 자신의 필드로 둠으로써 이 기반 구조체의 모든 기능성을 상속한다.

struct device {
struct list_head g_list;
struct list_head node;
struct list_head bus_list;
struct list_head driver_list;
struct list_head children;
struct list_head intf_list;
struct device *parent;
struct kobject kobj;
char bus_id[BUS_ID_SIZE];
:
:
};

kobject 에서 struct device로의 형변환에는 다음과 같은 매크로가 사용된다. 앞에서 나왔던 container_of 매크로를 사용하는 것일 뿐이다.

#define to_dev(obj)  container_of(obj,struct device,kobj)

이러한 개발 과정에서, 다른 여러 사람들은 동일한 시스템 이미지에서 더욱 많은 프로세서들이 실행될 수 있도록 규모가변성을 보장하기 위해 Linux 커널의 안정성을 개선하는 작업을 진행했다. 이 때문에, 여러 개발자들이 자신의 메모리 사용을 적절히 처리하기 위해 자신의 구조체에 참조 횟수를 추가하게 되었고, 각 개발자들은 구조체의 초기화, 참조 횟수 증가, 감소, 마무리를 위한 코드를 복제해야 했다.
그래서 struct kobject 의 간단한 기능을 뽑아 개별적인 구조체를 만들기로 했는데, 그 결과가 바로 struct kref 구조체이다.

struct kref 
{
atomic_t refcount;
};
struct kref는 단 세개의 단순 함수만을 가진다. kref_init는 참조 횟수를 초기화하고, kref_get은 참조 횟수를 증가하며, kref_put 은 참조 횟수를 감소한다. 처음 두 함수는 아주 간단하다. 살펴볼만한 것은 마지막 함수 뿐이다.

int kref_put(struct kref * kref, void (*release)(struct kref * kref))
{
WARN_ON(release == NULL);
WARN_ON(release == (void (*)(struct kref *))kfree);
if(atomic_dec_and_test(&kref->refcount)){
release(kref);
return 1;
}
return 0;
}

kref_put  함수는 두 개의 매개변수를 받는다. 하나는 참조 횟수를 감소할 struct kref를 가리키는 포인터이고 또 하나는 객체가 더이상 참조되지 않을 경우에 호출될 해제 함수를 가리키는 포인터이다.

함수의 처음 두 줄은 struct kref가 커널에 처음 추가될 당시에는 없었는데, 의도적으로 참조 횟수 관리를 피하려 드는 프로그래머들 때문에 추가한 것이다. 그들은 해제 함수에 대한 포인터를 아예 제공하지 않거나, 그에 대한 커널이 불평을 하면 기본 kfree 함수에 대한 포인터를 제공함으로써 참조 횟수 관리를 피하려 했다.

인수들이 이 두 점검들을 통과했다면 kref_put 함수는 참조 횟수를 원자적으로 감소한다. 그리고 이것이 객체에 대한 마지막 참조였다면, 주어진 해제 함수를 호출하고 1을 돌려준다. 마지막 참조가 아니었다면 0을 돌려준다ㅏ. 이 반환값은 호출자가 객체를 마지막으로 참조했던 것이 었는지의 여부를 알려줄 뿐, 객체가 여전히 메모리 안에 존재하는지를 알려주지는 않는다(호출이 반환된 이후에 다른 누군가가 객체를 해제할 수도 있으므로, 이 반환값으로 객체의 존재 여부를 보장할 수 없다).

struct kref가 도입되면서 struct kobject는 kref를 사용하도록 바뀌었다.

struct kobject{
char              name[KOBJ_NAME_LEN];
struct kref      kref;
:
:
}

이렇게 한 구조체 안에 다른 구조체를 집어넣는 방식을 적용한 결과, 원래의 struct usb_interface는 이제 하나의 struct device를 담게 되었고, 그것은 다시 struct kobject를 그것은 다시 struct kref를 담게 되었다. 


-- 장치 수천개로의 규모 확장

Linux는 휴대 전화, 무선조종 헬리콥터, 데스크톱, 서버는 물론 세계 최대 슈퍼 컴퓨터들의 73퍼센트에 이르기까지 거의 모든 플랫폼에서 실행된다. 그런 만큼 드라이버 모형의 규모가변성은 매우 중요한 사안이었으며, 항상 우리의 주된 관심사였다. 개발이 진행됨에 따라, 장치들을 담는데 쓰이는 구조체들, 즉 struct kobject와 struct device 가 비교적 작다는 점이 여러모로 도움이 되었다.

대부분의 시스템들에서 시스템에 연결된 장치 개수는 그 시스템의 크기와 정비례한다.
작은 임베디드 시스템의 경우에는 시스템에 연결된( 그리고 트리에 존재하는) 장치들이 하나에서 열 개 정도이다. 좀 더 큰 " 전사(enterprise)"시스템에는 그보다 훨씬 많은 장치들이 연결되지만, 그런 시스템들은 메모리도 넉넉하기 때문에,늘어난 장치들의 메모리 사용량은 여전히 커널의 전반적인 메모리 사용량의 아주 적은 부분을 차지한다.

그러나 "전사적" 시스템들 중에는 이런 느긋한 규모가변 모형이 완전히 깨지는 경우가 하나 있다. 바로 s390 메인프레임 컴퓨터이다. 이 컴퓨터의 경우 Linux가 하나의 가상 파티션에서 실행된다. 한 컴퓨터에서 최대 1,024개의 Linux 인스턴스들이 동시에 실행될 수 있고, 각 인스턴스에는 엄청나게 많은 개수의 서로 다른 저장 장치들이 연결된다. 전체적으로는 시스템의 메모리가 넉넉하지만 각 가상 파티션에는 그 메모리의 아주 작은 부분만 배정된다. 각 가상 파티션에 할당되는 메모리는 RAM 수백 메가 정도이나, 그래도 각 파티션은 서로 다른 저장 장치 모두(일반적으로 20,000개 정도)를 인식해야 한다.

이러한 시스템들에서는 장치 트리가 메모리의 상당 부분을 차지하게 되고, 그런 메모리는 결코 사용자 프로세스들에게 주어지지 않는다. 드라이버 모형의 살을 좀 빼야 할 때가 된 셈이었는데, 이 문제의 해결에 IBM의 똑똑한 커널 개발자 여러 명이 달라붙었다.

그 와중에 개발자들은 다소 놀라운 사실을 발견하게 되었다. 주된 struct device 구조체는 단 160 바이트 정도였고(32bit 프로세서의 경우), 따라서 시스템에 장치가 20,000개라고 해도 그 구조체들이 차지하는 메모리는 3,4MB밖에 되지 않았다. 이 정도는 충분히 감당할 수있는 수준이다. 실제로 메모리를 크게 잡아먹는 것은 앞에서 언급한, 모든 장치를 사용자공간에 노출하기 위한 RAM 기반 파일시스템 sysfs였다. 장치들 각각에 대해 sysfs는 하나의 struct inode와 하나의 struct dentry를 생성한다. 이들은 모두 상당히 뚱뚱한 구조체들로, struct inode는 약 256바이트 struct dentry는 약 140바이트이다.
각 struct device마다 적어도 하나의 struct dentry와 struct inode가 생성되었다. 전반적으로 이런 파일시스템 구조체들의 수많은 복사본들이 생성된다.
한 예로, 하나의 블록 장치는 약 10개의 서로다른 가상 파일들을 생성하며, 따라서 160바이트짜리 구조체 하나가 무려 4KB를 자치할 수도 있는 것이다. 장치가 20,000개인 시스템의 경우 가상 파일시스템이 약 80MB의 공간을 차지했다. 이 메모리는 커널이 소비하는 것이므로 어떠한 사용자 프로그램도 사용할 수 없었다. sysfs에 저장된 정보를 전혀 요구하지 않는 프로그램도 많다는 점을 감안한다면 이는 심각한 낭비였다.

이에 대한 해결책은 struct inode와 struct dentry 구조체들을 커널의 캐시에 넣되, 파일시스템 접근이 일어날 때마다 즉석에서 그것들을 생성하도록 sysfs의 코드를 재작성하는 것이었다. 구체적으로는, 장치가 처음 생성될 때 모든 것을 미리 할당해 두는 대신, 사용자가 트리를운행함에 따라 동적으로 디렉터리와 파일을 생성하는 방식이다. 이 구조체들은 커널의 주 캐시메모리 요구를 만족시킬 수 없으면 시스템은 캐시를 비워서 메모리를 확보하고 확보된 메모리를 애초에 메모리를 요구한 곳에 제공할 수 있다. 모든 변화는 sysfs의 백엔드 코드에서 일어난것으로, 주 struct device 구조체는 전혀 변하지 않았다.


-- 느슨하게 결합된 작은 객체들

Linux 드라이버 모형은 C언어를 이용해서 각자 한 가지 일에 특화된 여러개의 작은 객체들을 생성함으로써 고도의 객체지향적 코드 모형을 만드는 예를 보여준다. 이 객체들은 다른 객체들에 내장될 수 있으며,이를 통해서 아주 강력하고 유연한 객체 트리를 만들 수 있다. 이 객체들의 실제 사용량에 근거할때, 이들의 메모리 사용량은 최소한도이다. 이 덕분에, 동일한 코드 기반이 아주 작은 임베디드 시스템에서부터 세계 최대의 슈퍼컴퓨터들에 이르기까지 다양한 규모의 플랫폼에서 실행될 수 있을 정도로 Linux커널이 유연해 졌다.

이 모형의 개발은 또한 Linux 커널 개발이 진행되는 방식의 아주 흥미롭고도 강력한 두가지 측면을 보여준다.
우선, 공정이 아주 반복적(iterative)이다. 커널의 요구사항이 변함에 따라, 그리고 그 커널을 기반으로 작동하는 시스템의 요구사항이 변함에 따라, 개발자들은 모형에 좀 더 효율적으로 만들어야 하는 부분을 식별하고 추상화하는 방법을 발견할 수 있었다. 이는 변화하는 환경에서 시스템이 살아남는데 필요한 기본적인 진화적 요구에 대한 응답이었다.

두번째로 , 장치 처리의 역사는 그 공정이 극도로 협동적임을 보여준다. 서로 다른 개발자들이 커널의 여러 측면을 개선하고 확장하기 위한 다양한 아이디어를 제시한다. 그 외의 사람들은 소스 코드를 통해서 그 개발자들의 의도를 그들이 서술한 바 그대로 인식할 수 있으며, 원래의 개발자들이 결코 고려하지 않았던 방식으로 코드를 변화시키는 데 도움을 주게 된다. 이러한 협동작업의 결과로, 개발자가 따로 일하는 경우에는 결코 발견할 수 없었을 하나의 공통된 해결책을 얻어냄으로써 서로 다른 여러 개발자들의 목표가 달성된다.

커널 드라이버 모형 개발의 이러한 두 가지 특성은 Linux가 지금까지 만들어진 운영체제들 중 가장 유연하고 강력한 운영체제로 발전하는 데 도움을 주었다. 이런 특성들이 유지되는 한, Linux는 여전히 그러한 운영체제의 자리를 차지하게 될 것이다.




----이 글을 읽고 나서
독후감(?) 같은 느낌으로 마무리를 하게 되는 군요.
처음 책의 내용을 이렇게 글로 옮기게 된것은, 그냥 훓어보면서 도대체 이야기하고자 하는 바가 잘 안와닿아서,
차분히 읽을겸 옮겨적게 되었습니다..

우선 내가 느낀점은 이글은 완성된 드라이버 모델의 장점에 중심을 두기 보다는,
개발자들이 협업하면서 드라이버 모델이 발전해가는 과정에 중심을 두고 있다는 것이 개발자로서 와 닫는 부분이었습니다.

거의 모든 책들이 사용자나 개발 결과물에 대한 내용을 주로 다룬 것에 반해 개발자의 노력으로 개선되어가는 플랫폼을 보여주었으며, 뭐 크게 쇼킹하다고 생각되는 기술적인 내용은 아니지만, 현업에서 소소한것 하나하나들을 좀 살펴보고 개선의 여지가 있는 것들을 찾아보려는 노력을 게을리 하면 안되겟다는 생각을 다지게 되는 군요.

학생때 즐기면서 코딩을 시작했던 개발자 분들 !!! 
그래도 다른 일 하는 것 보다는 즐겁잖아요.!! 즐기면서 합시다.!!!


'Linux' 카테고리의 다른 글

프로세스의 메모리 사용량  (0) 2017.01.11
rpm 사용법  (0) 2014.02.13
Linux kernel : NMI 감시기  (0) 2009.12.31
kernel에서 user mode 로 정보 전달 방법  (0) 2009.12.30
[Windowing System] Linux X Server  (0) 2009.08.27
반응형
  • 바다폰 에서 미투 모바일 들너왔는데… 짱인데요 빨리 출시 했으면 좋겠다ㅠ..ㅜ(me2mobile me2mobile 직장인) 2010-07-07 17:03:39
  • 큭 간만에 집에 내려왔더니 아버지가 술 먹이시네…ㅋ(me2mobile 가족) 2010-07-10 02:11:01
  • 아…. 오늘 점심은 진짜 살기 위해서 먹었다.(식투미,me2mobile, 맛도 없고 감동도 없고 재미도 없고.....) 2010-07-14 12:57:32
  • 간만에 미투 하러 왔는데… 쓸말은 없고… 참 머리 잘랐답니다. ㅡㅡ; 싹뚝.(me2mobile, 2030) 2010-07-24 21:50:53
  • 웨이브 못사고 결국 겔스 사버렸네흠(웨이브 사고파 me2mobile) 2010-08-19 16:11:29
  • 아저씨. 과연 재미 있을까…(아저씨라 불리는게 그리 거부감이 느껴지지 않는 1인. me2mobile) 2010-08-22 21:02:57
  • 비가오내. .. 비가오면 생각나는 향숙이? ^^;(살인의 추억.. 을 떠오르게 했던 악마를 보았다. me2mobile) 2010-08-23 08:56:13
  • 광하니ㅎㅎ 부럽군요.. 퇴근길에 9900원짜리 회라도 사들고가야할듯
    회다~~ by 광하니 에 남긴 글 2010-08-26 15:08:45
  • 광하니ㅎㅎ 부럽군요.. 퇴근길에 9900원짜리 회라도 사들고가야할듯
    회다~~ by 광하니 에 남긴 글 2010-08-26 15:09:22
  • 광하니ㅎㅎ 부럽군요.. 퇴근길에 9900원짜리 회라도 사들고가야할듯
    회다~~ by 광하니 에 남긴 글 2010-08-26 15:10:57

이 글은 미카님의 2010년 7월 7일에서 2010년 8월 26일까지의 미투데이 내용입니다.

+ Recent posts