Objective-C의 기초 개념을 익히시려는 분들 반갑습니다. : )
Game Designer Snowrimp입니다.

Game Designer라 실망하시는 분들도 계시겠지만, 아래에 시작되는 튜토리얼은 Scott Stevenson이 작성한 튜토리얼을 번역해 둔것이니 안심하셔도 됩니다.

열심히 번역 공부도 할겸 시작한 일이라 매끄럽지 못하게 번역한 부분이나, 다소 의미의 차이가 있는 부분이 있을 수 있습니다. 이점은 저도 안타깝지만 조금씩 고쳐나갈 예정이며, 개선할 부분을 발견하신다면 과감히 덧글 부탁 드립니다. _ _)

이 포스팅으로 인해 아이폰의 메인 언어인 Objective-C의 초석을 다지는 일에 조금이나마 도움이 되셨으면 좋겠습니다.

또한 이 포스팅이 도움이 되셨다면, 원본 아티클에 기부하시는 한국인의 센스 부탁 드립니다.
Suggested amounts: Useful: $5 | Very Useful: $12 | Priceless: $21

 



Objective-C

Objective-C 는 맥 소프트웨어를 만들때 사용하는 주요 언어 입니다. 여러분이 기본적인 객체 개념과 C언어에 어려움이 없다면 Objective-C 의 감을 쉽게 잡을 수 있을 것입니다. 만약 C를 모른다면 먼저 C Tutorial 튜토리얼을 읽어 보십시오.

번역자노트. 위에서 링크된 C Tutorial의 번역본이 여기 있습니다. : )

이 튜토리얼의 작성과 일러스트는 Scott Stevenson 에 의해 쓰여졌습니다.
 
Copyright © 2008 Scott Stevenson


Calling Methods

가능한 빨리 시작해 보겟습니다, 아래의 간단한 예제들을 보시죠. object(이하 객체)에서 메소드를 호출하는 기본적인 문법 입니다:
 
[object method]; [object methodWithInput:input];
메소드는 값을 반환할 수 있습니다:
 
output = [object methodWithOutput]; output = [object methodWithInputAndOutput:input];
여러분들은 클래스들에서 메소드를 호출할 수 있습니다. 아래의 예제에서 있듯이, 우리는 NSString 객체를 반환하는 NSString 클래스의 string 메소드들를 호출할 수 있습니다:
 
id myObject = [NSString string];
Id 타입은 myObject 변수가 어떤 종류의 오브젝트든 참조할 수 있는것을 의미하므로, 여러분들의 어플리케이션을 컴파일할 때 실제 클래스와 매소드들을 모르는 경우 사용합니다.

이 예제에서, 오브젝트 타입이 NSString이 될 것이므로 우리는 타입을 바꿀 수 있습니다:
 
NSString* myString = [NSString string];
지금 이것은 NSString 변수이므로, 컴파일러는 NSString을 지원하지 않는 이 오브젝트의 메소드를 사용하고자 하면 우리에게 경고를 할 것입니다.

객체 타입의 오른쪽에 별표(*)가 있음을 주목하십시오. 모든 Objective-C  객체 변수들은 포인터 타입입니다. id 타입은 포인터 타입으로 미리 정의되어있으므로, 별표를 필요로하지 않습니다.
 

Nested Messages

많은 언어에서 내제되어 있는 메소드 또는 함수를 호출하는 방법은 이렇습니다:
 
function1 ( function2() );
function2의 결과는 function1의 인풋으로 전달됩니다. Objective-C 에서는 내제된 메시지를 이렇게 표현합니다:
 
[NSString stringWithFormat:[prefs format]];
읽기 어렵게 되기 쉽기 때문에, 한줄에 두개 이상의 내제된 메시지를 호출은 피하십시오.
 

Multi-Input Methods

어떤 메소드들은 입력변수를 여러개 가지고 있습니다. Objective-C 에서는, 메소드 이름을 몇가지 구분으로 나눌 수 있습니다. 헤더 부분에서 여러개의 입력변수를 가지고 있는 메소드를 이렇게 표현합니다:
 
-(BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile;
이 메소드를 다음과 같이 호출합니다:
 
BOOL result = [myData writeToFile:@"/tmp/log.txt" atomically:NO];
인수 이름은 없습니다. 런타임 시스템에서 메소드 이름은 실제로writeToFile:atomically 입니다.


Accessors

모든 인스턴스 변수들은 Objective-C에서 private가 기본 설정이므로, 여러분들은 거의 대부분의 경우 변수를 얻거나, 설정하기 위해서 accessor(이하 접근자)를 이용해야 합니다. 여기에 두가지 문법이 있습니다. 일단 전통적인 1.x 문법입니다:
 
[photo setCaption:@"Day at the Beach"]; output = [photo caption];
두번째 줄의 코드는 인스턴스 변수를 직접 읽지 않습니다. 이것은 실제로 caption 이라는 이름의 메소드를 호출하는 것입니다. 즉, Objective-C에서는 대부분의 경우 값을 얻기 위해 접두사 "get" 을 붙이지 않아도 됩니다.

언제든지 여러분들은 사각 꺽쇠 안의 코드를 보면 객체 또는 클래스에 메시지를 보내는 것이라고 이해하시면 됩니다.
 

Dot Syntax

Max OS X 10.5 파트의 일부인 Objective-C 2.0 에서 getter와 setter를 위해 dot 문법이 새롭게 등장 했습니다:
 
photo.caption = @"Day at the Beach"; output = photo.caption;
여러분들은 두가지 방법 모두를 사용할 수 있지만 각각의 프로젝트에서 하나의 방법을 사용해야 합니다. dot 문법은 setter와 getter에서만 사용되며, 메소드를 위한 것은 아닙니다.


Creating Objects

객체를 생성하는 두가지 방법이 있습니다. 첫번째 방법은 아래를 보시죠:
 
NSString* myString = [NSString string];
위의 방법은 좀더 편리한 오토매틱 스타일입니다. 이 경우, 여러분들은 autorelease된 객체를  생성할 수 있으며, 좀더 자세한 내용은 나중에 설명하겠습니다. 많은 경우, 여러분들은 메뉴얼 스타일을 이용해서 객체를 생성할 필요가 있습니다:
 
NSString* myString = [[NSString alloc] init];
이것은 중첩된 메소드 호출입니다. 첫번째는 NSString의 alloc 메소드입니다. 이는 메모리를 할당하고 인스턴트 객체를 생성하는 비교적 low-level의 호출입니다.

The second piece is a call to init on the new object. The init implementation usually does basic setup, such as creating instance variables. The details of that are unknown to you as a client of the class.

두번째 부분은 새로운 객체에서 init 하기위한 호출입니다. init 구현은 인스턴스 변수의 생성따위에서 기본적인 셋업을 수행하게 되는데, 그것들에 대한 자세한 사항은 클래스의 클라이언트이기 때문에 여러분들에게는 알려져있지 않습니다.

몇몇의 경우, 여러분들은 입력을 가지는 다른 버전의init을 사용하게 될 수 있습니다:
 
NSNumber* value = [[NSNumber alloc] initWithFloat:1.0];


Basic Memory Management

만일 여러분들이 Max OS X를 위한 어플리케이션을 작업 중이라면, 여러분들은 garbage collection을 사용할 수 있습니다. 일반적으로, 조금더 복잡한 경우가 아니라면 메모리 관리에 대해서 생각하지 않아도 됨을 의미합니다.

그러나, 여러분들은 항상 garbage collection을 지원받을 수 있는 환경이진 않을 것입니다. 이런경우, 여러분들은 몇가지 기본적인 컨셉(개념)을 알 필요가 있습니다.

만일 여러분들이 메뉴얼 alloc 스타일을 이용하여 객체를 생성했다면, 나중에 객체를 해재해줘야 합니다. 오토메틱 스타일로 생성한 객체는 메뉴얼 스타일로 해제하면 안되는데, 그 이유는 그렇게 할 경우 여러분의 어플리케이션이 clash가 발생할 수 있기 때문입니다.

여기에 두가지 예제가 있습니다:
 
// string1 will be released automatically NSString* string1 = [NSString string]; // must release this when done NSString* string2 = [[NSString alloc] init]; [string2 release];
이 튜토리얼을 통해서, 여러분들은 오토매틱 객체가 현재 함수의 끝에서 사라질 것임을 추측할 수 있습니다.

메모리 관리에 대해서 배울것이 더 있지만, 우리가 몇가지 컨셉들을 보고 더 알게된 다음에 배울 것입니다.


Designing a Class Interface

클래스 생성을 위한 Objective-C 문법은 매우 간단합니다. 전형적인 두 파트로 나눠집니다.

클래스 인터페이스는 ClassName.h파일에 담기고, 인터페이스 변수와 퍼블릭 매소드를 정의합니다.

구현부는 ClassName.m 파일 안에 있고, 이 메소드들의 실제 코드가 포함됩니다. 또한 클래스의 클라이언트에서 사용할 수 없는 private 메소드를 정의합니다.

여기에 인터페이스 파일의 예가 있습니다. 클래스 이름이 Photo 라서 파일 이름도 Photo.h 라고 이름지었습니다:
 
#import <Cocoa/Cocoa.h> @interface Photo : NSObject { NSString* caption; NSString* photographer; } @end
먼저, 우리는 코코아 어플리케이션을 위해서 기본적인 클래스들을 끌어쓰기 위해 Cocoa.h 를 임포트 합니다. #import directive(이하 지시어)는 하나의 파일을 여러번 include 하는 것을 자동으로 방지 해줍니다.

@interface는 이것이 Photo 클래스의 선언이다 라는 것을 말해줍니다. 콜론은 NSObject와 같은 superclass(이하 슈퍼클래스)를 명시할 때 사용합니다.

중괄호 안에는 두개의 인스턴스 변수가 있습니다:caption, photographer. 두개 모두 NSStrings 이지만, id를 포함한 어떤 오브젝트 타입도 될 수 있습니다.

마지막으로, @end 심볼은 클래스 선언의 종료를 의미합니다.
 

Add Methods

인터페이스 변수들를 위한 getter 몇개를 추가 해봅시다:
 
#import <Cocoa/Cocoa.h> @interface Photo : NSObject { NSString* caption; NSString* photographer; } - caption; - photographer; @end
Objective-C 메소드들은 일반적으로 "get" 접두어를 생략한다는 것을 기억합시다. 메소드 이름 앞의 하나의 '-'는 이것이 인터페이스 메소드이다 라는 것을 의미합니다. 메소드 이름 앞의 '+'는 이것은 클래스 메소드이다 라는 것을 의미합니다.

기본적으로, 컴파일러는 메소드가 하나의 id 객체를 반환할 것이고, 모든 입력 변수들이 id 일것 이라고 추정합니다. 위의 코드는 기술적으로 정확하지만 이것은 흔하지 않습니다. 반환 값을 위한 명확한 타입을 추가해 보겠습니다:
 
#import <Cocoa/Cocoa.h> @interface Photo : NSObject { NSString* caption; NSString* photographer; } - (NSString*) caption; - (NSString*) photographer; @end
이제 setter를 추가해 봅시다:
 
#import <Cocoa/Cocoa.h> @interface Photo : NSObject { NSString* caption; NSString* photographer; } - (NSString*) caption; - (NSString*) photographer; - (void) setCaption: (NSString*)input; - (void) setPhotographer: (NSString*)input; @end
setter는 반환값을 필요로 하지 않으므로, 우리는 그것들을 void로 명시합니다.


Class Implementation

구현부를 만들어 봅시다, getter로 시작합니다: 

#import "Photo.h"
@implementation Photo - (NSString*) caption { return caption; } - (NSString*) photographer { return photographer; } @end
코드 중 이 파트는 @implementation 과 클래스 이름으로 시작하고, interface와 같이 @end를 갖습니다. 모든 메소드들은 반드시 두 문장 사이에 있어야 합니다.

코드를 작성 해봤다면 getter는 메우 익숙해 보일 수 있어서, setter의 설명이 조금 더 필요할 것 같습니다:
 
- (void) setCaption: (NSString*)input { [caption autorelease]; caption = [input retain]; } - (void) setPhotographer: (NSString*)input { [photographer autorelease]; photographer = [input retain]; }
각각의 setter는 두개의 변수와 함께 다뤄집니다. 첫번째는 존재하는 객체를 참조하기 위함이고, 두번째는 새로운 입력 객체입니다. garbage collect가 적용된 환경에서 우리는 새로운 값을 직접 설정할 수 있었습니다:
 
- (void) setCaption: (NSString*)input { caption = input; }
하지만 만일 여러분들이 garbage collection을 이용할 수 없다면, 오래된 객체를 해재하고 새로운 것을유지해야 할 필요가 있습니다.

실제로 객체에서 참조를 자유롭게 하는 방법은 해제와 자동해제 두가지가 있다. 일반적인 해제는 참조를 즉시 제거해 준다. 자동해제 메소드는 일정시간 후에 해제를 하지만, 이는 분명히 현재의 함수가 종료 될 때 까지 머무르게 됩니다(만일 여러분들이 이것을 바꾸기 위한 특별한 코드를 작성하지 않았다면).

자동해제 메소드는 setter 안에서 더 안전한데, 이는 새롭고 오래된 값들을 위한 변수들이 같은 객체를 가리킬 수 있기 때문이다. 여러분들은 유지시키고 싶은 오브젝트가 바로 해제되는 것을 원하지 않을 것입니다. 

이러한 개념이 지금은 혼란스러울 수 있지만, 점점 여러분들이 나아가는데 도움이 될 것이다. 지금은 이것을 모두 숙지하지 않아도 됩니다.
 

Init

우리는 인스턴스 변수들을 위해 값들을 초기화는 init 메소들을 만들 수 있습니다:
 
- (id) init { if ( self = [super init] ) { [self setCaption:@"Default Caption"]; [self setPhotographer:@"Default Photographer"]; } return self; }
이것은 매우자명하긴 하지만, 두번째 줄이 아주 조금 낯설어 보입니다. 이것은 하나의 equals sign(=)이고, 이는 [super init]의 결과를 self에 할당하게 됩니다.

이것은 근본적으로 슈퍼클래스가 스스로 초기화 하게 요청하게 됩니다. if 구문은 기본값을 설정하기 전에 초기화가 성공적으로 이루어졌는가를 확인합니다.
 
 

Dealloc

dealloc 메소드는 메모리로 부터 객체가 제거될 때 객체에서 호출됩니다.. 이는 흔히 여러분들의 모든 child 인스턴스 변수들의 참조를 해제하기 위한 최선의 시점입니다.
 
- (void) dealloc { [caption release]; [photographer release]; [super dealloc]; }
첫번째 두 줄은 우리가 각각의 인스턴스 변수들을 해제하기 위해  메시지를 보낸 것입니다. 여기서 자동해제는 필요하지 않으며, 스탠다드 릴리즈가 조금더 빠릅니다.

마지막 줄은 매우 중요합니다. 우리는 수퍼클래스가 클린업하도록 요청하기 위해서 [super dealloc]메시지를 반드시 보내야 합니다. 만약에 우리가 이걸 하지 않으면, 객체는 제거되지 않을 것이고 메모리 누수가 발생할 것입니다.

dealloc 메소드는 grabage collection을 이용할 수 있다면 객체에서 호출하지 않습니다. 대신, 여러분들은finalize 메소드를 실행해야 합니다. 


More on Memory Management

Objecive-C의 메모리 관리 시스템은 reference counting 이라고 부릅니다. 여러분들은 반드시 여러분들의 참조들을 계속 파악하고 있어야하고, 런타임은 메모리를 해제합니다.

이해하기 쉽기 말하자면, 여러분은 어떤때에 객체를alloc 하거나, retain 하는데, 당신이 보낸 각각의 alloc/retain을 위해 하나의 해제를 보냅니다. 그리고 만일 여러분들이 alloc 을 한번, 그리고 retain을 한번 사용했다면, 해제를 두번 해야 합니다.
 
이것이 레퍼런스 카운팅의 이론이지만, 연습에서는 하나의 객체를 생성하기 위한 두가지 이유가 있습니다:

1. 인스턴스 변수를 유지하기 위해
2. 함수안에서 일시적으로 한번 사용하기 위해

대부분의 경우, 인스턴스 변수를 위한 setter는 오래된 객체를 자동 해제하고, 새로운 것을 유지합니다. 그런 후 여러분들은 dealloc 에서 해제가 잘 되었는지 반드시 확인해야 합니다.

그래서 진짜 일은 함수안의 지역 참조들을 관리하는 것입니다. 그리고 거기에는 오직 한가지 규칙이 있습니다: 만일 여러분들이 객체를 alloc 또는 copy와 함께 생성했다면, 해제 또는 자동해제 메시지를 함수의 종료 순간에 보내야 합니다. 다른 방법으로 객체를 선택했다면 그렇게 하지 않아도 됩니다.

인스턴스 변수를 관리하는 첫번째 경우가 여기에 있습니다:
 
- (void) setTotalAmount: (NSNumber*)input { [totalAmount autorelease]; totalAmount = [input retain]; } - (void) dealloc { [totalAmount release]; [super dealloc]; }
여기에 local references의 다른 경우가 있습니다. 우리는 오직 alloc 과 함께 생성된 객체만 해제하면 된다:
 
NSNumber* value1 = [[NSNumber alloc] initWithFloat:8.75]; NSNumber* value2 = [NSNumber numberWithFloat:14.78]; // only release value1, not value2 [value1 release];
그리고 여기에 콤보가 있습니다: 인스턴스 변수처럼 객체를 설정하기 위한 local reference를 사용하기:
 
NSNumber* value1 = [[NSNumber alloc] initWithFloat:8.75]; [self setTotal:value1]; NSNumber* value2 = [NSNumber numberWithFloat:14.78]; [self setTotal:value2]; [value1 release];
local reference를 관리하는 규칙은 그것들을 인스턴스 변수로 설정 했는지 하지 않았는지 고려하지 않아도 동일하다. 여러분들은 setter를 어떻게 구현할까에 대해선 생각하지 않아도 됩니다.

여러분들이 이것을 이해한다면, Objective-C의 메모리 관리에 대해 알아야할 부분 중 90%를 이해한 것입니다.


Logging

Objective-C 에서 Logging 메시지를 콘솔에 보내는 것은 매우 단순합니다. 사실, NSLog()가 객체를 위해 %@ 토큰을 추가 했다는 점을 제외하면 C의Printf() 함수와 유사합니다. 
 
NSLog ( @"The current date and time is: %@", [NSDate date] );
여러분들은 콘솔에 객체의 로그를 남길 수 있습니다. NSLog 함수는 객체에서 description 메소드를 호출할 수 있고 반환값을 NSString 으로 찍을 수 있습니다. 여러분들은 커스텀 스트링을 반환하게 하기 위해서 여러분들의 클래스 안의 description 메소드를 override(이하 오버라이드)할 수 있습니다.


Properties

우리가 caption 과 author 를 위해 접근자 메소드를 쓰기 전에 여러분들은 코드가 직관적이고 일반적일 수 있다는 것을 알아차릴 수 있을 것입니다.

Properties 는 우리에게 Objective-C 에서 접근자를 자동으로 만들어낼 수 있게 허락하는 특징을 가지고 있고, 또한 몇가지 다른면으로장점을 가지고 있습니다. properties를 이용하여 Photo 클래스를 컨버팅 해봅시다.

여기에 예전과 같은 모습의 코드가 있습니다:
 
#import <Cocoa/Cocoa.h> @interface Photo : NSObject { NSString* caption; NSString* photographer; } - (NSString*) caption; - (NSString*) photographer; - (void) setCaption: (NSString*)input; - (void) setPhotographer: (NSString*)input; @end
여기에 properties로 컨버팅된 코드가 있습니다:
 
#import <Cocoa/Cocoa.h> @interface Photo : NSObject { NSString* caption; NSString* photographer; } @property (retain) NSString* caption; @property (retain) NSString* photographer; @end
@property 는 특성을 선언하는 Objective-C 지시자 입니다. 괄호 안의 "retain"은 setter가  입력값을retain 하도록 명시하고, 그리고 나머지 줄은 간단하게 타입과 특성의 이름을 명시합니다.

지금 바로 클래스의 구현부를 봅시다:
 
#import "Photo.h" @implementation Photo @synthesize caption; @synthesize photographer; - (void) dealloc { [caption release]; [photographer release]; [super dealloc]; } @end
@synthesize 지시자는 우리를 위해 자동으로 setter와 getter를 만들어 주므로, 이 클래스를 위해 우리가 해야하는 일은 dealloc 메소드 입니다. 

접근자는 이미지 존재하는 것이 아니라면 생성만 할 수 있으므로, property를 위해 자유롭게 @synthesize를 명시하면 되고, 만일 여러분들이 원한다면 여러분들의 커스텀 getter 또는 setter를 구현할 수 있습니다. 컴파일러는 어느쪽이든 잃어버린 메소드들을 채워 줄 것입니다.

property를 선언하는 많은 다른 방법들이 있지만 이 튜토리얼에서는 다루지 않을 것입니다.
 


Calling Methods on Nil

Objective-C 에서, nil 객체는 다른 많은 언어의 NULL 포인터와 상응하는 기능입니다. 다른 언어와 다른점은 충돌이나, 예외없이 nil 메소드를 호출할 수 있다는 점입니다.

이 테크닉은 프레임워크에서 다양한 방법으로 사용되고 있지만, 이것이 의미하는 가장 중요한 점은 지금 당장 객체에서 메소드를 호출하기 전에 nil을 체크하지 않아도  된다는 것입니다. 만일 여러분들이 객체에서 반환된 nil 메소드를 호출한다면, 여러분들은 반환값으로 nil을 갖게됩니다.

우리는 또한 이것을 우리의 deallc 메소드를 개선시키는 것에 이용할 수 있습니다:
 
- (void) dealloc { self.caption = nil; self.photographer = nil; [super dealloc]; }
이렇게 해도 작동하는데 이는 우리가 인스턴스 변수로 nil을 설정 했을 때 setter는 nil을 retain 하고 오래된 값을 release 하기 때문입니다. 이러한 접근은 종종 dealloc 보다 나은데 왜냐하면 변수가 있었던 객체의 랜덤데이터를 가리킬 염려가 없기 때문입니다.

우리는 self.<var> 문법을 이용하고 있다는 점을 명심하십시오. 이는 우리가 setter를 이용하고 있다는 것과 메모리 관리를 자유롭게 이용하는 것을 의미하고 있습니다. 만일 우리가 아래와 같이 값을 직접 설정하면 메모리 누수가 발생 할 것이다:
 
// incorrect. causes a memory leak. // use self.caption to go through setter caption = nil;


Categories

Categories는 Objective-C에서 가장 유용한 특징입니다. 요점만 말하자면, 카테고리는 존재하는 클래스에 서브클래스 없이, 또는 그것들이 어떻게 구현되었나에 대한 자세한 정보를 알 필요 없이 메소드를 추가할 수 있도록 허락 해줍니다.

만들어진 객체에 메소드를 추가할 수 있기 때문에 이것은 매우 유용합니다. 만일 여러분들이 여러분들의 어플리케이션 안에서 NSString의 모든 인스턴스에 메소드를 추가하기를 원하다면, 단지 하나의 카테고리를 추가하기만 하면 됩니다. 커스텀 서브클래스를 사용하기 위해서 모든것을 가질 필요가 없습니다.

예를들면, 컨텐츠가 URL인지 확인하기 위해 NSString에 메소드를 추가를 원한다면 저는 이렇게 구현할 것입니다:
 
#import <Cocoa/Cocoa.h> @interface NSString (Utilities) - (BOOL) isURL; @end
이건 클래스를 선언하는 모습과 매우 습사합니다. 다른점은 수퍼클래스의 리스트가 없고, 괄호에 카테고리를 위한 이름이 있다는 것입니다. 이름은 여러분들이 원하는 무엇이는지 될 수 있지만, 안에 있는 메소드와 커뮤니케이션 해야합니다.

여기에 구현부가 있습니다. 이 예가 URL 검출의 좋은 구현예는 아니지만, 우리는 카테고리에 대한 컨셉을 배우는 것에 중점을 둡시다:
 
#import "NSString-Utilities.h" @implementation NSString (Utilities) - (BOOL) isURL { if ( [self hasPrefix:@"http://"] ) return YES; else return NO; } @end
이제 여러분은 어떤 NSString 에서도 이 메소드를 사용할 수 있습니다. 아래의 코드는 "string1 is a URL"을 콘솔에 출력할 것 입니다:
 
NSString* string1 = @"http://pixar.com/"; NSString* string2 = @"Pixar"; if ( [string1 isURL] ) NSLog (@"string1 is a URL"); if ( [string2 isURL] ) NSLog (@"string2 is a URL");
서브클래스와 다르게, 카테고리는 인스턴스 변수를 추가할 수 없습니다. 하지만, 클래스 안에 이미 존재하는 메소드를 오버라이드(override)하기 위해 카테고리를 이용할 수 있지만, 매우 조심해야 합니다.

기억해야 합니다, 여러분들이 카테고리를 이용해서 클래스로 변경시킬 경우에, 이것은 어플리케이션 전체에 걸친 클래스의 모든 인스턴스에 영향을 줄 수 있습니다.


Wrap Up

이것은 Objective-C의 기본적인 개요입니다. 다른 언어를 경험한적이 있다면 매우 쉽게 익힐 수 있습니다. 특별한 문법을 배우기 위해서는 충분하지 않으며, Cocoa 전반에 걸쳐 같은 예제가 계속해서 사용되었습니다.

여러분들이 이 예제들을 실행해 보고 싶으시다면, 아래의 프로젝트를 다운로드 받으시고, 소스 코드를 살펴 보십시오:
LearnObjectiveC Xcode 3.0 Project (56k)
 
고맙습니다.


긴 번역이 끝났습니다. 서둘러 번역하느라, 이해하지 못한 부분도 있어서 어떤 부분은 반쪽짜리 번역이 되어 버렸네요. 한번 번역으로 끝나지 않고, 지속적인 모니터링을 통해서 퇴고해나갈 예정입니다.

위 글은 Objective-C에 대한 기본적인 개념들의 개요를 간단한 예제를 통해 소개하고 있는데요, 객체에 대한 좀더 자세한 설명이 서두에 있었으면 하는 아쉬움이 남습니다.

그래서 다음에 기회가 되면, 
Objective-C 에서 객체, 클래스, 인터페이스, 메소드 등에대한 이야기를 해볼까 합니다. 그 글이 완료되면 이 포스팅에도 링크를 걸어둘테니 관심 있으신 분들은 가끔씩 생각나실때 보시면 될 듯 합니다.

미숙한 번역 포스팅이 노력하시는 분에게 작은 도움이 되었으면 좋겠네요.
즐거운 하루 되세요 : )


http://snowhouse-textcube.blogspot.kr/2010/01/%EC%95%84%EC%9D%B4%ED%8F%B0-%EA%B0%9C%EB%B0%9C-%EC%99%84%EC%A0%84-%EC%B4%88%EB%B3%B4%EB%8F%84-%EB%B3%B4%EB%8A%94-objective-c-%EA%B8%B0%EC%B4%88%EA%B0%9C%EB%85%90.html

http://blog.naver.com/PostView.nhn?blogId=wisereign&logNo=30094792319&parentCategoryNo=&categoryNo=&viewDate=&isShowPopularPosts=false&from=postView

출처 : http://krunivs.tistory.com/43

한 프로세스 혹은 시스템에서 만들 수 있는 쓰레드의 갯수(동시작업)는  주로

/usr/include/sys/pthread.h

에 보면 나타나있습니다.

회사 AIX 는 아래와 같군요

#ifdef _LARGE_THREADS
#define MAXTHREADS  32767       /* max number of additional threads   */
                    /* per process.  This number + 1 must */
                    /* be a multiple of 128           */
#else
#define MAXTHREADS  512     /* max number of threads per process, */
                    /* MUST be a multiple of 128, assuming*/
                    /* uthread is padded to 32 bytes and a*/
                    /* page is 4096-byte long         */
#endif

어떻게 커널이 컴파일 되었는지 모르면 아래와 같은 코드로 출력을 해볼 수도 있습니다.

cout << sysconf(_SC_THREAD_THREADS_MAX) << endl;

제가 현재 사용하는 서버에서는 32768 개의 동시 쓰레드를 생성할 수 있습니다.

과연???  한번 아래와 같은 코드로 출력을 해보았습니다.

void* threadFunction(void*)
{
    cout << "Thread : " << ::count++ << endl;
    return NULL;
}

int main()
{
    while (true)
    {
        pthread_t thread;
        pthread_attr_t attr;
   
        pthread_attr_init(&attr);

        int ret = pthread_create(&thread, &attr, threadFunction, NULL);
    
        if (ret != 0)
        {
            cout << strerror(ret) << endl;
            return 0;
        }

        usleep(1000);
    }

출력은 아래와 같이 됩니다. 

.......
Thread : 2304
Thread : 2305
Thread : 2306
Thread : 2307
Thread : 2308
Resource temporarily unavailable

당연한 결과라 사료됩니다. 쓰레드 Function 이 바로 종료가 되지만, CPU 관점에서 본다면 바로 종료가 될수는 없으리라 여겨집니다.

이번엔 아래와 같이 코드를  수정했씁니다.

        int ret = pthread_create(&thread, 0, threadFunction, NULL);
        pthread_detach(thread);

결과는 아래와 같습니다. 
Thread : 101420
Thread : 101421
Thread : 101422
.....

예상대로 끊임없이 돕니다.

이번엔 바로 종료되는 쓰레드가 아닌 루프를 돌렸습니다.

void* threadFunction(void*)
{
    cout << "Thread : " << ::count++ << endl;

    void* retr;
    while (true)
    {
        sleep(1);
    }
    return NULL;
}

결과입니다.

.....
Thread : 2152
Thread : 2153
Thread : 2154
Thread : 2155
Resource temporarily unavailable

detatch 를 해줘봐야 종료가 안되는 쓰레드이니 자원반납의 의미가 없습니다.

그럼 쓰레드 Function 이 루프가 없었던 경우, detach 한 경우와 하지 않은 경우에서 detach 를 한경우는 (끊임없이 쓰레드 생성을 하는 것으로 봐서) 쓰레드의 종료가 일어 나면서 사용했던 메모리(thread stack size) 를 반납을 해주어서 끊임없이 생성이 가능 했다고 할 수 있습니다.

Stevens 가 쓴 Advanced Programming in the UNIX Environment 에서 보면 쓰레드 반납을 위해  pthread_join, 혹은 pthread_detach 를 꼭해주어야 한다고 나와 있습니다.

일례로 쓰레드 Function 에서 pthread_exit() 를 명시적으로 호출해준다면..

void* threadFunction(void*)
{
    cout << "Thread : " << ::count++ << endl;
    pthread_exit((void*) 1);
}

이코드 역시 쓰레드 생성중 Resource 부족으로 프로그램이 종료됩니다.
pthread_cancel() 역시 마찬가지입니다. 

쓰레드 생성시 join 을 호출 하지 않을 거라면(일반적인 멀티 쓰레드 서버 어플리케이션이라면 join 호출할일이 그다지 없습니다) 항상 detach 를 해주시는게 도움이 됩니다.

보통 쓰레드를 생성하실경우 아래와 같이 하시면 도움이 될듯합니다.

typedef void*                   (*startFunc)(void*);
void startThread(startFunc run, void* arg)
{
    pthread_t       t;
    pthread_attr_t  attr;

    if (pthread_attr_init(&attr) != 0 ||
        pthread_create(&t, &attr, run, arg) != 0 ||
        pthread_attr_destroy(&attr) != 0 ||
        pthread_detach(t) != 0)
    {
//        CRITICAL("Can't Start Thread");
    }
}


사족으로 쓰레드의 스택 사이즈가 얼마나 먹길래 몇개 생성도 안해서 죽어버리는지 알고 싶으시면 (시스템에따라 1000~500 개 under 일 경우가 대부분입니다)


C++과 대부분은 비슷하지만 Objective C 만의 문법이 있어서 정리해본다.

아무리 무작정 따라하기 라지만, 역시 기본 문법도 모르고는 앞으로 나아가기가 쉽지 않다.

그렇다고 또 한권짜리 문법책을 읽기엔 너무 지루해서 흐름이 멈춘다.

다행히 기본 문법을 한페이지(?)로 간략히 메모해놓은 사이트가 있다.

앞에서 정리한 Objective-C의 특징과 겹치는 부분들도 있지만 훨씬 이해하기 편하게 적혀있다.


원글 Learn Objective-C by Scott Stevenson
작성자 블로그의 첫페이지에 있는 말.


All of the buildings, all of those cars were once just a dream in somebody's head. — Peter Gabriel
(모든 빌딩과 모든 자동차들도 원래는 누군가의 머리속의 꿈에 지나지 않았다.)


<순서>
1.메소드의 호출
2.접근자
3.오브젝트의 생성
4.기본 메모리 관리법
5.클래스 Interface (.h)
6.클래스 Implementation (.m)
7.메모리 관리법 좀더 살펴보기
8.로깅
9.프로퍼티
10.Nil 오브젝트의 메소드 호출
11.카테고리


1.메소드의 호출 +-----+-----+-----+-----+-----+


기본 문법
[object method];
[object methodWithInput:input];


다른 언어에서 말하는 오브젝트명.메소드명 이 여기서는 [오브젝트 메소드] 괄호에 둘러쌓여있고 스페이스로 구분하는 형식이 된다.

파라메터를 가지는 경우에는 [오브젝트 메소드:파라매터] 로 불린다. (메소드와 파라매터사이에 콜론이 존재)

리턴값이 있는 경우
output = [object methodWithOutput];
output = [object methodWithInputAndOutput:input];


클래스 메소드도 같은 문법으로 호출 가능하다.
id myObject = [NSString string];


여기서 id란 myObject가 어떤 오브젝트의 참조도 될 수 있다는 뜻이며,
즉 컴파일 할 때에도 클래스 타입을 알지 못한다는 뜻이다.


하지만 여기선 딱 보면 NSString이란걸 알 수 있기에 아래와 같이도 쓸수 있다.
NSString* myString = [NSString string];


오브젝트 타입 옆에 *(아스타리스크)가 있는걸 볼 수 있다.
모든 Objective-c의 오브젝트 변수들은 포인터 타입이다.
id 타입은 이미 포인터 타입으로 정의 되어 있기 때문에 *(아스타리스크)를 붙일 필요가 없는것 뿐이다.


Nasted 메세지


일반적인 많은 프로그래밍 언어에서 Nasted 메소드나 함수를 호출할 때 아래와 같은 문법을 사용한다.
function1 ( function2() );


이것이 Objective-C에선 아래와 같이 표현된다.
[NSString stringWithFormat:[prefs format]];


다중 파라매터를 가지는 메소드 호출


이런 경우, Objective-C에서는 메소드 명을 여러 조각으로 나누어 사용한다.
-(BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile;


위의 메소드를 호출하는 방법은 아래와 같다.
BOOL result = [myData writeToFile:@"/tmp/log.txt" atomically:NO];


결국 이 말은 파라매터명이란 것이 존재 하지 않는 다는 뜻이며,
런타임 시스템에서 메소드 명은 writeToFile:atomically: 로 인식된다.

2.접근자 +-----+-----+-----+-----+-----+


Objective-c의 모든 인스턴스 변수들은 private이다. 즉 접근자를 사용하여 억세스 하여야 한다는 소리이다.


일반적인 문법


[photo setCaption:@"Day at the Beach"];
output = [photo caption];


두번째 줄의 코드는 인스턴스 변수를 직접 읽는다는 뜻이 아니라 caption이라는 메소드를 부르고 있는 것이다.
Objective-C에서는 일반적으로 getter 메소드에 get을 붙이지 않고 사용한다.


.(dot)를 이용한 문법


.(dot)는 접근자(getter and setter)를 위해 Objective-C 2.0에 새로 추가된 문법이다.


photo.caption = @"Day at the Beach";
output = photo.caption;


동일한 프로젝트 내에서는 한쪽 문법만 사용하는 것이 통일성 있게 코딩하는 방법이다.
또한 .(dot)는 일반 메소드 호출에는 사용할 수 없음을 명심하자.

3.오브젝트의 생성 +-----+-----+-----+-----+-----+


오브젝트를 생성하는 방법은 여러가지가 있다.


자동할당


NSString* myString = [NSString string];


자동으로 생성하여 할당해주는 편리한 방법이다. 자세한 내용은 나중에 살펴보도록 한다.
하지만 대부분의 오브젝트는 아래에 설명하는 수동 할당 방법을 사용하여야 한다.


수동할당


NSString* myString = [[NSString alloc] init];


이것은 메소드의 Nested된 호출방식이다.
우선 NSString클래스의 alloc메소드를 부른다.
이는 매우 하위레벨의 호출로써 메모리를 확보하고 오브젝트를 인스턴스화 한다.


다음으로 생성된 오브젝트의 init 메소드를 부른다.
init은 주로 초기화 작업을 하는데 예를 들면 내부 변수들의 인스턴스를 생성하는 작업 등이다.


값을 가지는 다른 버젼의 init을 호출하는 경우도 있다.


NSNumber* value = [[NSNumber alloc] initWithFloat:1.0];

4.기본 메모리 관리법 +-----+-----+-----+-----+-----+


만약 Mac OS X에서 돌아가는 어플리케이션을 개발한다면 garbage collection을 사용할 수 있다.
다시 말하자면 복잡한 경우를 제외하고는 메모리 관리를 하지 않아도 된다는 뜻이다.


하지만 우리는 항상 garbage collection을 제공하는 환경하에서 작업하는 것이 아니기 때문에
기본 메모리 관리법을 알아 두도록 하자.


alloc을 이용하여 수동적으로 오브젝트를 생성하였을 경우에는 이를 release 하는 작업을 해야 한다.
반대로 자동으로 할당해주는 방법으로 생성한 오브젝트는 수동으로 release 하면 안된다.
이는 프로그램에 문제를 발생시킨다.


// string1 는 자동으로 release 될 것이다.
NSString* string1 = [NSString string];


// string2 는 사용이 끝난후 수동으로 release 해주어야 한다.
NSString* string2 = [[NSString alloc] init];
[string2 release];


여기서 string1 는 함수의 끝에서 자동으로 release된다고 생각하면 된다.


메모리 관리에 대해서 좀 더 알아둘 내용이 있지만
몇가지 다른 개념을 먼저 알고 가는 것이 좋음으로 뒤쪽에서 다시 보도록 한다.

5.클래스 Interface (.h) +-----+-----+-----+-----+-----+


Objective-C에서 클래스를 만드는 방법은 매우 간단하다.
.h 파일에 인터페이스를 적고 .m 파일에 내용을 적으면 된다.


.h 파일
인스턴스 변수와 퍼블릭 메소드의 인터페이스를 정의한다.


.m 파일
.h 에 정의한 인터페이스들을 implement한다.
그리고 private 메소드 들도 여기에 적어준다.


<.h 파일의 예>
#import <Cocoa/Cocoa.h>

@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}

//getters - 일반적으로 getter에 get을 붙이지 않는다는것은 위에서 언급한바 있다.
- (NSString*) caption;
- (NSString*) photographer;

//setters - setter에서 리턴값이 필요없다면 void로 지정한다.
- (void) setCaption: (NSString*)input;
- (void) setPhotographer: (NSString*)input;
@end


여기서 import한 Cocoa.h는 Cocoa 어플리케이션을 위한 기본 클래스들을 포함한다.
#import 명령은 하나의 파일을 여러번 읽어들이는 것을 자동적으로 방지해준다.

6.클래스 Implementation (.m) +-----+-----+-----+-----+-----+


위에서 정의한 Photo클래스의 .m 부분을 살펴보자.


#import "Photo.h"


@implementation Photo


- (NSString*) caption {
return caption;
}
- (NSString*) photographer {
return photographer;
}


@end


모든 메소드 내용들은 @implementation과 @end 사이에 기술된다.
이 getter 메소드들은 특별히 설명하지 않아도 친숙한 코딩방법이므로 넘어가도록 한다.


setter를 추가해보자.


- (void) setCaption: (NSString*)input
{
[caption autorelease];
caption = [input retain];
}


- (void) setPhotographer: (NSString*)input
{
[photographer autorelease];
photographer = [input retain];
}


settter는 좀 설명이 필요할 듯 하다.
하나의 setter안에는 두 변수가 사용된다. 하나는 기존의 오브젝트의 참조이고 하나는 파라매터로 넘어온 새로운 오브젝트이다.
만약 garbage collection을 사용할 수 있는 환경이라면 단순히 아래와 같이 적어주면 된다.


- (void) setCaption: (NSString*)input {
caption = input;
}


하지만 그렇지 않다면 첫번째 예제와 같이 release와 retain을 수동적으로 적어줘야 할 것이다.
우선 release 방법에는 release 와 autorelease 의 두가지 방법이 있는데,
스탠다드 release는 오브젝트 참조를 바로 삭제하는 방법이고, autorelease는 가까운 시일내에 release할 것을 명령하는 방법이다.
단, autorelease를 지정한 경우에 기존 오브젝트는 현재 실행되고 있는 함수 안에서는 유효하다고 볼 수 있다.


setter메소드 안에서는 autorelease 를 사용할 것을 추천한다.
왜냐하면 새 오브젝트와 기존 오브젝트가 같은 오브젝트를 가르키고 있는 경우도 있기 때문이다.
이럴 경우 스탠다드 release를 사용해 버리면 원치 않게 새로 오브젝트를 삭제해 버리는 경우가 생긴다.


이 부분이 지금 바로 이해가 가지 않는다고 해도 좀 더 공부하면 서서히 알게 될 것이니 걱정하지 않아도 된다.

Init


인스턴스 변수들을 초기화하기 위해 Init 메소드를 정의한다.


- (id) init
{
if ( self = [super init] )
{
[self setCaption:@"Default Caption"];
[self setPhotographer:@"Default Photographer"];
}
return self;
}


여기서 if 문의 조건은 == 이 아니라 = 이다.
즉, 같다면 이라는 비교문이 아니라 [super init]의 결과를 self 에 대입하고 있다는 뜻이다.
이 예제 메소드에서는 슈퍼클래스에게 초기화를 대신 요청하고 그 결과에 문제가 없으면 다른 변수들에 기본값을 세팅하고 있다.

Dealloc


dealloc 메소드는 오브젝트가 메모리에서 삭제될때 불려진다.
고로 이 메소드 안에서 자신이 가지고 있는 인스턴스 변수들의 참조를 release 해 주면 된다.


- (void) dealloc
{
[caption release];
[photographer release];
[super dealloc];
}


클래스의 멤버 변수 였던 caption과 photographer는 그냥 스탠다드 release를 이용하여 release 한다.
이게 더 빠른 방법이므로 여기서 autorelase를 이용할 필요는 없다.


마지막 줄의 [super dealloc]은 슈퍼클래스에게 메모리 release를 요청하는 것인데,

만약 이것을 까먹는다면 memory leak 문제가 발생할 수 있을 것이다.


dealloc메소드는 garbage collection이 이용되고 있는 환경에서는 불려지지 않으며 대신 finalize 메소드를 정의한다.

7.메모리 관리법 좀더 살펴보기 +-----+-----+-----+-----+-----+


Objective-C에서는 참조카운터라는 메모리 관리 수법을 사용한다.


오브젝트를 생성(alloc) 하거나 보유(retain - 메소드 파라매터로 넘어올때) 할때 참조 카운터는 1씩 증가한다.
생성한 오브젝트를 retain 하면 카운터는 2가 되어있을 것이며 우리는 각각의 alloc 과 retain에 대해 release를 해줘야 한다.


하지만 실제로 우리가 오브젝트를 생성하는 경우는 아래 2가지 상황 뿐이다.


1.클래스의 인스턴스 변수로 사용할때
2.함수안에서 임시로 사용할 때


1번의 인스턴스 변수는 주로 autorelease와 dealloc에서 release 해주게 된다.


그럼 우리가 해결해야 할 것은 2번의 로컬변수들인데, 이것도 결국은 같은 이치로 alloc 이나 copy로 생성된 오브젝트를 사용후
함수안에서 autorelease나 release해주면 되는 것 뿐이다.


<1의 예제>
- (void) setTotalAmount: (NSNumber*)input
{
[totalAmount autorelease];
totalAmount = [input retain];
}


- (void) dealloc
{
[totalAmount release];
[super dealloc];
}


<2의 예제>
NSNumber* value1 = [[NSNumber alloc] initWithFloat:8.75];
NSNumber* value2 = [NSNumber numberWithFloat:14.78];


// value2 는 alloc으로 생성하지 않았기 때문에 release 할 필요가 없다.
[value1 release];


이부분이 이해 된다면 Objective-C 의 메모리 관리의 90%를 파악했다고 할 수 있다.\

※ retain (오브젝트의 보유) 에 관하여

수신한 오브젝트를 파기하고 싶지 않은 경우도 있다. 예를 들어 인스턴스변수에 오브젝트를 캐쉬할 필요가 있을지도 모른다.
이런 경우, 오브젝트가 필요한지 어떤지 알수 있는건 본인 뿐임으로 사용중인 오브젝트를 파기당하지 않도록 보증하는 수단이 필요해진다.이럴때 바로 retain 메소드를 사용한다. 이 메소드는 보류중인 autorelease 효과를 정지시켜 그 오브젝트가 필요없어 질때까지 relase되지 않도록 보장해준다.

8.로깅 +-----+-----+-----+-----+-----+


Objective-C의 로깅은 매우 심플하다. NSLog 함수가 거의 printf() 와 유사하기 때문이다.
단, %@ 라는 오브젝트를 표현하는 포멧이 추가되어 있다.


NSLog ( @"The current date and time is: %@", [NSDate date] );


NSLog를 이용하여 오브젝트를 콘솔에 찍으면 사실 오브젝트의 description메소드가 불려짐으로,
이를 오버라이드하여 원하는 결과를 찍어볼 수 있다.

9.프로퍼티 +-----+-----+-----+-----+-----+


Objective-C의 특징에 설명되어 있음으로 예제만 나열하도록 한다.


위에서 봐 온 getter와 setter가 적혀있는 예제를 property를 이용하여 아래와 같이 간략해진다.

<Photo.h>
#import <Cocoa/Cocoa.h>

@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
@property (retain) NSString* caption;
@property (retain) NSString* photographer;

@end


<Photo.m>
#import "Photo.h"

@implementation Photo

@synthesize caption;
@synthesize photographer;

- (void) dealloc
{
[caption release];
[photographer release];
[super dealloc];
}

@end


@synthesize는 소스내에 정의되어 있지 않은 getter/setter만 자동생성 해주기 때문에 안심하고 사용할 수 있다.

10.Nil 오브젝트의 메소드 호출 +-----+-----+-----+-----+-----+


Objective-C의 nil 오브젝트는 다른 프로그래밍 언어에서 말하는 NULL 오브젝트와 동일한 개념이다.
한가지 다른점은 nil인 오브젝트의 메소드를 호출하여도 에러가 나지 않는다는 점이다.


이것은 여러가지 방법으로 활용되지만 가장 편리한 점은 널 체크를 하지 않고 메소드를 호출할 수 있다는 점이다.


또한 dealloc의 성능을 높이기 위해서 사용할 수도 있다.
- (void) dealloc
{
self.caption = nil;
self.photographer = nil;
[super dealloc];
}


여기서 nil을 인스턴스 변수의 값으로 넘기면 setter는 nil을 retain하고 기존 오브젝트를 release 한다.
이 방법은 dealloc에서 매우 효율적인데, 왜냐하면 nil을 세팅하므로써 변수가 다른 오브젝트를 참조할 가능성이 없어지기 때문이다.


self.를 붙여 setter를 호출해야하지 caption = nil; 이라고 하면 잘못된 방법이므로 memory leak를 초래한다.

11.카테고리 +-----+-----+-----+-----+-----+

카테고리는 Objective-C의 유용한 특징중에 하나이다.
요점부터 말하자면 카테고리는 기존의 클래스의 서브클래스를 생성하지 않고도 메소드를 추가할 수 있다.
(확장하고자 하는 클래스의 내부구조를 파악할 필요가 없다.)


이는 매우 유용한데, 왜냐하면 예를 들어 NSString에 그냥 카테고리를 추가만 하면 그 프로젝트 안에서 카테고리로서 추가된 메소드를 사용 할 수 있기 때문이다. 즉, NSString을 계승하여 새 클래스를 만들어 그것을 alloc하여 쓴다.. 하는 복잡한 과정이 필요없다는 뜻이자.


예제를 살펴보자.

<헤더>
#import <Cocoa/Cocoa.h>

@interface NSString (Utilities)
- (BOOL) isURL;
@end

<클래스>
#import "NSString-Utilities.h"

@implementation NSString (Utilities)

- (BOOL) isURL
{
if ( [self hasPrefix:@"http://"] )
return YES;
else
return NO;
}

@end


일반 클래스와의 차이는 단지 슈퍼클래스의 정의가 없고 클래스 명 뒤의 괄호안에 카테고리 명을 써 준 다는 점 뿐이다.


위에서 추가한 메소드를 사용해 보는 예제를 살펴보자
NSString* string1 = @"http://pixar.com/
";
NSString* string2 = @"Pixar";

if ( [string1 isURL] )
NSLog (@"string1 is a URL");

if ( [string2 isURL] )
NSLog (@"string2 is a URL");


카테고리는 서브클래스 확장과는 달리 인스턴스 변수는 추가 할 수 없다.
하지만 기존의 메소드를 오버라이드 하는것은 가능한데 이는 매우 신중히 해야할 작업중 하나이다.


기억해둬야 할 중요한 사항은, 클래스 카테고리를 사용한 변경은 어플리케이션의 모든 클래스 인스턴스에 영향을 준다는 사실이다.

출처 : http://firekokoma.tistory.com/13


많은 개발자들이 리눅스용으로 어플리케이션을 작성하고 있었습니다만 솔라리스10의 수많은 새로운 기능과 썬의 계속되는 AMD, 인텔 프로세서 지원을 통해 개발자들이 솔라리스 플랫폼에서 어플리케이션을 작성하는 것에 대해 관심을 가지게되었습니다. 이 글은 각 운영체제에서의 개발 환경의 유사점과 차이점에 대해 살펴 봅니다. 이 글을 통해 어플리케이션을 리눅스에서 솔라리스로 포팅 하거나, 리눅스에서 개발 경험이 있는 개발자가 솔라리스에서 프로그래밍을 하는 데 도움을 얻을 수 있을 것입니다.

이 글에서 용어 "솔라리스"는 솔라리스 10 (과 오픈 솔라리스)를 나타내고 "리눅스" 는 리눅스 2.6을 나타 냅니다. 이 글에서 다루는 많은 부분들은 솔라리스와 리눅스의 이전 버젼에도 또한 적용될 것입니다. 이 글의 테스트는 SuSe 9.1 하에서 이루어 졌으나 리눅스 배포판이란 범용 리눅스를 뜻합니다. 또한 이 글은 C++ 도 동일하게 수행되겠지만 C 프로그래밍을 작성하는데 촛점을 맞추고 있습니다. 자바 기반의 어플리케이션은 리눅스 혹은 솔라리스에 특정한 함수를 호출해서는 안되는 관계로 어플리케이션은 포팅이 가능할 것입니다.

소개

이 글에서는 솔라리스와 리눅스에서 어플리케이션 프로그래머와 분석가들에게 보여질 수 있는 동일점과 차이점에 대해 생각해 봅니다. 꼭 이것이 차이점에 대한 소모적인 설명을 뜻하거나 어떠한 OS가 상대편 보다 좋다는 것을 보여주는 것은 아닙니다. 이 글은 한쪽 OS에 경험이 많은 개발자가 다른쪽 OS 또한 쉽게 다를 수 있도록 돕는데 그 목적이 있습니다.

POSIX과 호환되며 어떠한 시스템 콜 혹은 라이브러리 함수도 호출하지 않는 간단한 어플리케이션은 어떠한 수정도 없이 두 OS 상에 포팅이 가능할 것입니다. 즉 어플리케이션을 작성하고 솔라리스 혹은 리눅스에서 컴파일 한 다음 다른 OS 에서 간단히 재 컴파일하면 두 곳 모두에서 작동할 수 있습니다. 대부분의 시스템 콜 과 라이브러리 루틴은 이와 같은 것입니다.

리눅스의 많은 시스템 콜들은 솔라리스에 라이브러리 함수 형태로 존재하며, 또한 그 반대도 마찬가지입니다. 예를 들어sched_setscheduler() 는 리눅스에서 시스템 콜이고 솔라리스에서는 priocntl(2) 시스템 콜을 호출 하는 라이브러리 함수 입니다.priocntl(2) 시스템 콜은 리눅스에서는 존재 하지않습니다만 리눅스는 시간 공유, 실시간 이상의 멀티 스케쥴러를 지원하지 않습니다. 뒷부분에서 시스템콜을 기능적 섹션으로 그룹을 묶고 각 OS에서 어떠한 것이 사용 가능한지 비교해 봅니다.

리눅스 세계의 대부분의 어플리케이션과 툴킷은 어떠한 수정도 없이 컴파일이 가능할 것입니다. 여기에는 gcc, emacs, MySQL, perl 외 수많은 것들이 포함되어있습니다. 컴파일된 바이너리 패키지는 http://www.sunfreeware.com 에서 다운로드할 수 있습니다.

리눅스와 솔라리스를 비교 하는 몇몇 글이 존재 하지만 대부분은 이전 버전들을 비교하는 글이었습니다. 이런 글들은 웹에서 "리눅스 솔라리스 비교 " 로 검색해 볼 수 있습니다. Seal Rock Research White Paper (pdf) 은 솔라리스10 과 리눅스 2.6 버젼에 대해 다루고있습니다.Migrating Solaris Applications to Linux 은 솔라리스 어플리케이션을 리눅스로 포팅 하는 것에 대해 몇장에 걸쳐서 다루고 있습니다.

솔라리스와 리눅스는 많은 관리상의 차이점이 존재하며, 한 OS의 각 배포판별로도 많은 차이점이 있습니다 . 솔라리스10은 솔라리스 이전 버젼에 비해 가장 크게 변화된 "서비스 관리 프레임워크(SMF;Service Management Framework)" 라는 기능을 가지고 있습니다. 시스템 관리상의 차이점은 이 글에서 다루지 않지만 개발자에게 영향을 미치는 경우 추후 다루도록 하겠습니다.

시스템 콜과 라이브러리

리눅스에 존재하는 대부분의 시스템 콜과 라이브러리는 솔라리스에도 존재합니다. 이번 문단에서는 두 시스템 간에 각각 다른 시스템 콜과 라이브러리 루틴에 대해 다룹니다.

솔라리스는 시스템 콜의 리스트를 /usr/include/sys/syscall.h 에 보관 합니다. 리눅스는 동일한 정보를 /usr/include/asm/unistd.h에 보관 합니다. (주목할 점은 리눅스와 솔라리스 둘다 unistd.h 와 syscall.h 파일을 가지며 종종 동일한 내용을 가질때도 있다는 사실입니다.)

시스템 콜에 대한 문서는 솔라리스, 리눅스에서 모두 /usr/share/man/man2 에 존재합니다. (솔라리스는 /usr/man 에서 동일한 곳으로 심볼릭 링크가 걸려 있습니다.) 라이브러리 루틴은 메뉴얼의 다양한 섹션에 문서화되어있습니다. 리눅스, 솔라리스의 라이브러리 섹션에 대한 개관을 위해 man intro.3 를 보도록 합니다. 주목할 점은 솔라리스는 라이브러리 루틴을 리눅스보다 더 잘 분류해 놓았다는 점입니다. 예를 들어 aio_read() 는 솔라리스에서 aio_read(3RT) 에 분류되어 있지만 리눅스에서는 aio_read(3)에 분류되어 있습니다. 결과적으로 솔라리스에서 aio_read() 를 사용하는 프로그램을 컴파일할 때 개발자는 반드시 실시간 라이브러리 -lrt 를 통해 실시간 라이브러리를 컴파일/링크 할때 포함시켜야 하지만 리눅스에서는 그럴 필요가 없습니다.

리눅스와 솔라리스는 둘다 200개 이상의 서로 다른 라이브러리를 가지고 있고 라이브러리 내에 50,000 개 정도의 함수를 가지고 있습니다.

다음의 표는 리눅스와 솔라리스의 몇몇 라이브러리를 목록화한 것입니다. 주의할 점은 이것이 전체 목록은 아니라는 것이고 이러한 라이브러리들은 기본 인스톨이 된 시스템에서는 각각 따로 다운로드 및 설치를 해야 한다는 것입니다.

표 1: 리눅스와 솔라리스의 몇몇 라이브러리들
 
 
솔라리스
리눅스
설명
libc
libc
표준 C 라이브러리 (POSIX, SysV, ANSI, 등등.) 솔라리스의 libc man 참조.
libucb
libc
UCB (University California Berkeley) 호환 바이너리
libmalloc
libc
몇가지 서로 다른 malloc 라이브러리가 존재함; 기본 함수는 libc 에 존재.
libsocket
libc
소켓 라이브러리 (리눅스에서는 libc 에 존재).
libxnet
libc
X/Open 네트워킹 라이브러리
libresolv
libresolv
DNS 루틴들 (솔라리스에서는, inet_* routines)
libnsl
libnsl/libc
네트워크 서비스 라이브러리 (리눅스 - nis/nis+ 루틴들)
librpc
librpc
RPC 함수들
libslp
libslp
Service Location Protocol
libsasl
libsasl
간단한 인증과 보안 레이어
libaio
libaio
비동기 I/O 라이브러리
libdoor
 
Door 지원 (door_create()door_return(), 등등.)
librt
librt
POSIX 실시간 라이브러리
libcfgadm
 
설정 관리 라이브러리
libcontract
 
계약 관리 라이브러리 (솔라리스에서 man contract.4 참조)
libcpc
 
CPU 성능 카운터 라이브러리 (리눅스에서는 아마 커널 모듈을 설치해야 할 것임)
libdat
 
직접 접근 전송 라이브러리 (http://www.datcollaborative.org)
libelf
libelf
ELF 지원 라이브러리
libm
libm
수학 라이브러리

다음 섹션에서는 몇가지 시스템 콜과 라이브러리들에 대해 좀 더 자세히 알아 봅니다. 시스템 간의 차이점에 대해 집중하겠습니다.

소켓과 네트워킹

대부분의 소켓, 네트워킹 코드는 반드시 사용하는 OS에 따라 다시 재컴파일 되어야 하고 반드시 실행이 가능해야 합니다. 이 섹션은 솔라리스와 리눅스에서 보편적으로 사용되는 네트워크에 관련된 시스템 콜과 라이브러리를 비교합니다.

socket()

socket() 루틴, 이에 덧붙여서 AF_UNIXAF_INET, 와 AF_INET6 도메인 매개변수들은 솔라리스와 리눅스에서 추가적인 값을 각각 가지고 있습니다. 솔라리스에서 AF_NCA 도메인은 소켓에 네트워크 캐쉬 혹은 가속기 (nca(1) 참조)를 지정하여 사용 할 수 있도록 합니다. 대부분의 어드레스 패밀리(도메인들)은 리눅스, 솔라리스에 둘다 존재 합니다. 주의: 솔라리스에 /usr/include/sys/socket.h 그리고 리눅스에/usr/include/linux/socket.h 를 비교하여 가능한 어드레스 패밀리를 참고 하시기 바랍니다. 그러나 개발자는 몇몇 이러한 도메인을 지원하는 코드를 작성 혹은 다운로드 받아야 할지도 모릅니다.

리눅스는socket(2) 맨 페이지에 기술된 대로 몇가지 추가적인 도메인을 가지고 있습니다. 기술된 추가적인 도메인은 다음과 같습니다:

  • AF_IPX - Novell IPX 프로토콜(아마도 오로지 수세를 위한 것임?).
  • AF_NETLINK - 커널/유저 인터페이스 디바이스로 유저가 커널 모듈에 접근 할 수 있도록 허락해 줌. 주의: 솔라리스에서는 다른 방법으로 똑같은 일을 할 수 있는 방법이 존재 함(리눅스에서도 마찬가지).
  • AF_X25 - X25 프로토콜. 솔라리스에서 이 도메인은 Solstice X.25 제품에 포함되 있음.
  • AF_AX25 - 아마츄어 라디오 AX.25 프로토콜.
  • AF_ATMPVC - ATM 상의 영구적인 가상 서킷.
  • AF_APPLETALK - 리눅스의 man ddp 참고. 솔라리스에도 존재 하지만 문서화 되어 있지는 않음.
  • AF_PACKET - 리눅스의 man packet.7 참고. Raw 패킷 인터페이스. 솔라리스에서는 NIC 디바이스를 열고 use getmsg(2)/putmsg(2) 를 사용하여 DLPI를 통한 raw 패킷을 전송/수신 할 수 있음. (DLPI에 대한 자세한 설명은 Data Link Provider Interface (DLPI), Version 2 를 참조).
bind()

리눅스 멘 페이지 (man bind.2) 는 AF_INET 와 AF_UNIX 사이의 어드레스 패밀리 차이점에 대한 정보를 포함하고 있음. 솔라리스는 man bind.3socket 을 참조.

listen()

리눅스와 솔라리스 둘다 backlog 매개 변수는 (listen()의 두번째 매개변수) 수락되길 기다리고 있는 접속된 큐의 길이를 나타 내고 있음. 리눅스 맨 페이지에서는 이렇게 설명 하고 있고 솔라리스 멘 페이지에서는 그냥 "지연되고 있는 연결 큐"라고 설명되어 있습니다.

accept()

리눅스는 3가지의 연결 기반 소켓 타입을 지원합니다: SOCK_STREAMSOCK_SEQPACKET, 그리고 SOCK_RDM 반면 솔라리스에서는 오직SOCK_STREAM 만 기술 되어 있습니다. 리눅스 구현에서는 몇가지 소켓 플래그를 상속 하지 않습니다. 이 것이 구현의 차이점을 불러 왔을 것입니다.

connect()

리눅스 멘페이지는 (man connect.2SOCK_SEQPACKET 를 기술 하고 있지만 솔라리스에서는 기술되어 있지 않습니다. 리눅스는 struct sockaddr 내의 sa_family 를 AF_UNSPEC 으로 세팅해서 주소에 연결 함으로써 비연결 소켓과 connect() 을 구분하고 있습니다. 이러한 동작은 솔라리스에는 기술되어 있지 않습니다.

send()/recv()

다른 소켓 라이브러리 함수들과 같이, 이러한 함수들은 두 시스템에서 거의 동일하게 동작합니다. 리눅스는 멘페이지에서 몇가지 추가적인flags 매개 변수를 가지고 있습니다.

shutdown()

솔라리스와 리눅스간에 주목할만한 차이점은 없습니다.

네트워킹 예제

몇가지 차이점이 들어나는 어플리케이션을 확인 하는 것이 유용할 것입니다. tracedump 프로그램은 패킷 캡춰 라이브러리 (libpcap) 를 이용하여 유저 레벨에서의 이더넷 패킷을 읽어 들입니다. 이 코드는 raw 이더넷이 솔라리스, 리눅스 간에 상당히 다름을 알아 낼 수 있습니다. (libpcap 은 FreeBSD, HP-UX, AIX 같은 서로 다른 시스템 간의 차이점을 알아 내는데 유용할 수 있습니다.) libpcap에서 응용 가능한 코드는pcap-linux.c 와 pcap-dlpi.c에 존재합니다. DLPI 코드는 솔라리스, HP-UX, AIX, 그리고 다른 운영체제를 위해 사용 됩니다. 리눅스는 표준socket 호출을 통해 raw 소켓 패킷을 읽어 들일 수 있는 방법을 제공합니다. 솔라리스는 getmsg(2) 와 putmsg(2) 를 호출해서 DLPI 패킷을 주고 받습니다.

다음의 코드는 솔라리스의 네트워크 인터페이스에서 유저-레벨 패킷을 캡춰 하는 방법을 나타내고 있습니다. 바로 뒤에 리눅스에서 동일한 작업을 하는 코드가 따라 옵니다. 이 코드는 libpcap 라이브러리의 매우 엄청난 간략화 버젼입니다.

#include <sys/types.h>
#include <sys/dlpi.h>
#include <sys/stream.h>
#include <stdio.h>

#include <errno.h>
#include <stropts.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

int
main(int argc, char *argv[])
{
    register char *cp;
    int fd;
    dl_info_ack_t *infop;
    union DL_primitives dlp;
    dl_info_req_t inforeq;
      dl_bind_req_t    bindreq;
    dl_attach_req_t attachreq;
    dl_promiscon_req_t promisconreq;
    struct    strbuf    ctl, data;
    int    flags;
    char buffer[8192];
    dl_error_ack_t *edlp;

    fd = open(argv[1], O_RDWR);  /* for instance, /dev/elxl0 */

    /* attach to a specific interface */
    attachreq.dl_primitive = DL_ATTACH_REQ;
    attachreq.dl_ppa = 0;  /* assume we want /dev/xxx0 */
    ctl.maxlen = 0;
    ctl.len = sizeof(attachreq);
    ctl.buf = (char *)&attachreq;
    flags = 0;
    /* send attach req */
    putmsg(fd, &ctl, (struct strbuf *)NULL, flags);
    ctl.maxlen = sizeof(dlp);
    ctl.len = 0;
    ctl.buf = (char *)&dlp;
    /* get ok ack, may contain error */
    getmsg(fd, &ctl, (struct strbuf*)NULL, &flags);

    memset((char *)&bindreq, 0, sizeof(bindreq));
    /* the following bind might not need to be done */
    bindreq.dl_primitive = DL_BIND_REQ;
    bindreq.dl_sap = 0; 
    bindreq.dl_max_conind = 1;
    bindreq.dl_service_mode = DL_CLDLS;
    bindreq.dl_conn_mgmt = 0;
    bindreq.dl_xidtest_flg = 0;
    ctl.maxlen = 0;
    ctl.len = sizeof(bindreq);
    ctl.buf = (char *)&bindreq;
    flags = 0;
    /* send bind req */
    putmsg(fd, &ctl, (struct strbuf *)NULL, flags);
    ctl.maxlen = sizeof(dlp);
    ctl.len = 0;
    ctl.buf = (char *)&dlp;
    /* get bind ack */
    getmsg(fd, &ctl, (struct strbuf*)NULL, &flags);

    promisconreq.dl_primitive = DL_PROMISCON_REQ;
    promisconreq.dl_level = DL_PROMISC_PHYS;
    ctl.maxlen = 0;
    ctl.len = sizeof(promisconreq);
    ctl.buf = (char *)&promisconreq;
    flags = 0;
    /* send promiscuous on req */
    putmsg(fd, &ctl, (struct strbuf *)NULL, flags);
    ctl.maxlen = sizeof(dlp);
    ctl.len = 0;
    ctl.buf = (char *)&dlp;
    /* get get ok ack */
    getmsg(fd, &ctl, (struct strbuf*)NULL, &flags);

    promisconreq.dl_primitive = DL_PROMISCON_REQ;
    promisconreq.dl_level = DL_PROMISC_SAP;
    ctl.maxlen = 0;
    ctl.len = sizeof(promisconreq);
    ctl.buf = (char *)&promisconreq;
    flags = 0;
    /* send promiscuous on req */
    putmsg(fd, &ctl, (struct strbuf *)NULL, flags);
    ctl.maxlen = sizeof(dlp);
    ctl.len = 0;
    ctl.buf = (char *)&dlp;
    /* get get ok ack */
    getmsg(fd, &ctl, (struct strbuf*)NULL, &flags);

    /* read and echo to stdout whatever comes to us */
    while (1) {
      data.buf = buffer;
      data.maxlen = sizeof(buffer);
      data.len = 0;
      ctl.buf = (char *) &dlp;
      ctl.maxlen = sizeof(dlp);
      ctl.len = 0;
      flags = 0;
      getmsg(fd, &ctl, &data, &flags);
      write(1, "\nCTL:\n", 6);
      write(1, ctl.buf, ctl.len);
      write(1, "\nDAT:\n", 6);
      write(1, data.buf, data.len);
    }
}

솔라리스 코드는 DLPI 요청을 만들고 DLPI 응답을 받아서 인터페이스에게 인터페이스에 도착하는 모든 패킷의 카피를 만들도록 요청 합니다.

리눅스의 코드는 훨씬 간단합니다. socket(2) 콜은 raw 패킷을 지정할 수 있도록 허락합니다. 리눅스는 DLPI나 STREAMS을 사용하지 않습니다.

#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>

#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <net/if_arp.h>
#include <stdio.h>

int
main(int argc, char *argv[])
{
    int    sock_fd = -1;
    struct sockaddr_ll    sll, from;
    struct packet_mreq    mr;
    socklen_t    fromlen;
    int        packet_len;
    char        buffer[8192];

    sock_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

    memset(&sll, 0, sizeof(sll));
    sll.sll_family    = AF_PACKET;
    sll.sll_ifindex    = 0;
    sll.sll_protocol    = htons(ETH_P_ALL);

    bind(sock_fd, (struct sockaddr *) &sll, sizeof(sll));

    while (1) {
      fromlen = sizeof(from);
      packet_len = recvfrom(
        sock_fd, buffer, sizeof(buffer), MSG_TRUNC,
        (struct sockaddr *) &from, &fromlen);
      write(1, buffer, packet_len);
    }
}

프로세스/프로세서 관리

솔라리스와 리눅스에서프로세스 는 프로그램의 동작하고 있는 실체 입니다. 솔라리스와 리눅스(2.6)에서 프로세스는 주소 공간을 위한 컨테이너 이자 하나 혹은 이상의 쓰레드 입니다. 시스템의 모든 프로세스들은 특수한 프로세스 ID(PID)를 가지고 있고 프로세스가 종료 된 후에도 일정 기간동안 유일성이 유지됩니다. 프로세스들은 fork(2) 와 이와 비슷한 동작을 하는 것들에 의해 만들어 집니다. 리눅스에서 프로세스(그리고 쓰레드)는 clone(2)을 사용하여 생성될 수 있습니다. 그러나 pthread_create(3) 가 좀더 포팅이 용이 합니다. 솔라리스에서 기술되지 않은 lwp_create() 시스템 콜은 어떠한 면에서 clone(2)과 비슷합니다.

vfork() 는 두 시스템에서 모두 비슷하게 동작합니다. 솔라리스는 fork1() 과 forkall() 을 가지고 있습니다. fork1() 의 경우, 자식 프로세스는 오직 fork() 콜을 실행 시키는 쓰레드를 가지도록 합니다; forkall() 의 경우, 부모 프로세스 안의 모든 쓰레드는 자식에게 복사 됩니다. 기본 fork 는 fork1() 입니다. forkall() 은 반드시 명시적으로 사용되어야 합니다. forkall() 은 리눅스에서는 존재 하지 않습니다.

ps -elfL 커맨드는 리눅스와 솔라리스 둘다에서 프로세스 내의 쓰레드를 확인 하는데 사용될 수 있습니다. 두 시스템 모두 LWP 의 숫자와 프로세스내의 각 쓰레드의 lwpid 를 보여 줍니다. 알아둘 점은 리눅스에서 lwpid 는 프로세스들 간에서도 유일한 숫자 입니다. 솔라리스에서lwpid는 프로세스 안에서만 유일합니다. 리눅스에서 멀티쓰레드 프로세스의 프로세스 ID는 사실 쓰레드 그룹의 ID입니다. 쓰레드 그룹 ID는 메인 쓰레드의 프로세스 ID와 동일 합니다. Sending a signal to any lwpid 에 시그널(kill(1)/kill(2)을 이용해서) 을 보내는 것은 프로세스에 시그널을 보내는 것과 동일 합니다. 솔라리스에서 사용자는 pid 로 시그널을 보냅니다. 두경우에서 만약 기본 동작이 취해진다면, 프로세스는 일반적으로 종료되고 쓰레드는 모두 중지 됩니다. ps(1) 맨페이지에서 자세한 사항을 참고 바랍니다.

리눅스와 솔라리스는 둘다 프로세서에 프로세스를 바인딩 하는 구문을 제공합니다. 리눅스는 각 프로세서를 상호배타적으로 사용한다는 가정하에 바인딩을 허락 합니다. 솔라리스는 배타적인 사용을 위해 프로세서의 셋에 바인딩 하는 것을 허락합니다. (CPU fencing 이라 불림) 그러나 그룹을 상호배타적인 용도로 바인딩 하는 것을 허락 하지 않습니다 (솔라리스 존은 제외). 리눅스는 CPU fencing 메카니즘을 가지고 있지 않습니다. 그러나 웹에서 관련된 구현은 찾을 수 있습니다. (예들 들어 bullopensource.org 사이트의 CPUSETS for Linux page ). 바인딩 작업을 하는 리눅스 시스템 콜은 sched_setaffinity(2) 와 sched_getaffinity(2) 입니다. 솔라리스에서는 다음과 같습니다:

  • processor_bind(2) 는 프로세서에 LWP 혹은 프로세를 바인드/언바인드
  • pset_create(2) 프로세서 셋을 셋팅
  • pbind(1) 와 psrset(1) 은 커맨드 라인 인터페이스를 제공

완벽한 설명을 위해 ps(1) 커맨드의 리눅스, 솔라리스 에서의 출력이 Threads 섹션에 기록되어 있습니다.

리눅스와 솔라리스 시스템에서 모든 형태의 exec 시스템 콜은 execve(2) 를 결과적으로 호출 하도록 되어 있습니다. 솔라리스 문서는 같은 맨 페이지에 6가지의 exec(2) 종류를 제공하고 있습니다. 리눅스의 exec(3) 맨 페이지에서는 execvexeclexecleexeclp, 그리고execvp 가 기록되어 있스빈다. 분리된 페이지로 execve(2) 를 다루고 있습니다.

/proc 파일 시스템은 리눅스와 솔라리스 상에서 약간 상이 합니다. 두개의 시스템에서 모두 /proc 는 현재 시스템에서 동작하고 있는 프로세스들의 ID들을 파일 이름으로 가지고 있는 파일들을 포함 합니다. 각 PID 로 명명된 파일들은 디렉토리가 됩니다. 리눅스의 /proc 는 다양한 다른 프로세스 이름과 더불어 다양한 다른 이름의 디렉토리들을 포함합니다. 이러한 것들의 대부분은 프로세서, 디바이스, 시스템 통계를 다루고 있습니다. 리눅스에서 사용자는/proc 을 통해 프로세스, 프로세서, 디바이스, 메인 아키텍쳐 등의 정보를 얻습니다. 솔라리스에는 이러한 종류의 정보를 보통 커맨드를 통해 얻습니다. 예를 들어 prtconf(1) 는 솔라리스의 머신 설정 정보를 얻는데 사용될 수 있습니다. 리눅스에서는 /proc 에 존재하는 파일을 조사 함으로써 정보를 얻습니다.

솔라리스에서 프로세스가 사용하는 가상 주소 공간에 대한 정보는 pmap(1) 을 사용하여 얻을 수 있고 리눅스에서는 /proc/pid/maps 파일을 봄으로써 얻을 수 있습니다. 솔라리스의 pmap(1) 과 리눅스의 proc(5) 맨페이지를 참고 하시기 바랍니다.

<-- on solaris, address space of this instance of bash -->
bash-3.00$ pmap -x $$  
1043:    /usr/bin/bash -i
 Address  Kbytes     RSS    Anon  Locked Mode   Mapped File
08045000      12      12       4       - rw---    [ stack ]
08050000     528     468       -       - r-x--  bash
080E3000      76      72       8       - rwx--  bash
080F6000     124     108      40       - rwx--    [ heap ]
FED8E000       4       4       -       - rwxs-    [ anon ]
FEDA0000       4       4       -       - rwx--    [ anon ]
FEDB0000     760     660       -       - r-x--  libc.so.1
FEE7E000      24      24       8       - rw---  libc.so.1
FEE84000       8       8       -       - rw---  libc.so.1
FEE90000      24       8       4       - rwx--    [ anon ]
FEEA0000     524     324       -       - r-x--  libnsl.so.1
FEF33000      20      20       4       - rw---  libnsl.so.1
FEF38000      32       -       -       - rw---  libnsl.so.1
FEF50000      44      40       -       - r-x--  libsocket.so.1
FEF6B000       4       4       -       - rw---  libsocket.so.1
FEF70000       4       4       4       - rwx--    [ anon ]
FEF80000     144     132       -       - r-x--  libcurses.so.1
FEFB4000      28      24       -       - rw---  libcurses.so.1
FEFBB000       8       -       -       - rw---  libcurses.so.1
FEFC0000       4       4       -       - r-x--  libdl.so.1
FEFC7000     140     140       -       - r-x--  ld.so.1
FEFFA000       4       4       4       - rwx--  ld.so.1
FEFFB000       8       8       4       - rwx--  ld.so.1
-------- ------- ------- ------- -------
total Kb    2528    2072      80       -
bash-3.00$ 

리눅스에서 동일한 정보는 아래의 그림 1 을 참고 바랍니다. 알아둘 점은 리눅스는 라이브러리의 완전한 경로 이름을 보여 준다는 것입니다.(결과는 라이브러리 이름을 보여주기 위해 약간 수정 되었음) 솔라리스에서 라이브러리의 완전한 경로 이름을 얻기 위해서는 pldd(1)를 사용합니다 .

 
사용자 삽입 이미지
그림 1: 리눅스에서 프로세스에 의해 사용된 가상 공간 주소를 검사


쓰레드

리눅스와 솔라리스는 POISIX 쓰레드를 지원 합니다. 리눅스는 The Native POSIX Thread Library for Linux 를 통해서 솔라리스는 표준 C 라이브러리의 일부로 제공하고 있습니다. Multithreaded Programming Guide 에 특별히 Chapter 5 Programming with the Solaris Software,를 참고하시기 바랍니다. Multithreading in the Solaris Operating Environment 문서도 추천할 만 합니다.

POSIC 쓰레드에 덧붙여서 솔라리스는 "솔라리스 쓰레드"를 제공합니다. threads(5) 맨 페이지는 POSIX 쓰레드 라이브러와 솔라리스 쓰레드 라이브러리의 유사점과 차이점에 대해 설명하고 있습니다. 구현은 상호 운용이 가능하고 동일한 어플리케이션에서 조금의 주의를 기울이면 같이 사용할 수 있습니다. 다음의 내용은 맨 페이지에서 바로 가져온 것입니다.

동일점

libpthread 와 libthread 라이브러리에 존재하는 대부분의 함수들은 각각의 라이브러리에 동일한 역할을 하는 함수를 가지고 있습니다. 세마포어 이름을 제외한 POSIX 함수의 이름은 "pthread" 프리픽스를 가지고 있습니다. POSIX와 솔라리스의 비슷한 함수 들은 유사한 끝부분을 가지고 있습니다. 전형적으로 POSIX 함수와 솔라리스 함수들은 같은 숫자의 인수들을 가지고 있습니다.

차이점
  • POSIX 쓰레드가 좀더 포팅이 용이 합니다.
  • POSIX 쓰레드는 설정이 가능한 속성 오브젝트에 따라 각 쓰레드에 특성을 가진 쓰레드를 만드는 것이 가능합니다.
  • POSIX 쓰레드는 쓰레드 취소를 구현하고 있습니다.
  • POSIX 쓰레드는 스케쥴링 알고리즘을 준수하고 있습니다.
  • POSIX pthreads 는 fork(2) 콜을 위한 클린-업 핸들러를 허용하고 있습니다.
  • 솔라리스 쓰레드는 중지/재개가 가능합니다.
  • 솔라리스 쓰레드는 견고한 뮤텍스 락을 가진 인터프로세스를 구현하고 있습니다.
  • 솔라리스 쓰레드는 데몬 쓰레드를 구현하여 프로세스를 종료할때 대기하지 않아도 됩니다.

아래의 프로그램은 간단한 멀티 쓰레드 프로그램입니다. 각 OS 에서 멀티 쓰레드 어플리케이션이 동작하는데에는 큰 차이가 없음을 알 수 있습니다. 물론 바닥에 있는 구현방법에는 몇가지 차이점이 있을 것입니다.

#include <pthread.h>
#include <stdio.h>

void *fcn(void *);

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

    pthread_create(&tid, NULL, fcn, NULL);
    (void) printf("main thread id = %x\n", pthread_self());
    pthread_join(tid, NULL);
}

void *
fcn(void *arg)
{
    printf("new thread id = %x\n", pthread_self());
}

다음을 이용해서 솔라리스 플랫폼에서 프로그램을 컴파일 하고 수행 시킵니다:

bash-3.00$ cc simplepthread.c -o simplepthread
bash-3.00$ ./simplepthread
main thread id = 1
new thread id = 2
bash-3.00$ 

솔라리스에서 gcc 를 사용 하면 똑같은 결과를 얻을 수 있을 것입니다. 리눅스에서는 다음과 같이 나타 납니다:

max@linux:~/source> cc simplepthread.c
/tmp/cc8u7kZs.o(.text+0x1e): In function `main':
simplepthread.c: undefined reference to `pthread_create'
/tmp/cc8u7kZs.o(.text+0x4a):simplepthread.c: undefined reference
       to `pthread_join'
collect2: ld returned 1 exit status
max@linux:~/source> cc simplepthread.c -lpthread -o simplepthread
max@linux:~/source> ./simplepthread
main thread id = 4015c6c0
new thread id = 4035cbb0
max@linux:~/source> 

리눅스에서 POSIX 쓰레드 라이브러니는 반드시 명시적으로 링크되어야 합니다. 알아둘 점은 솔라리스 9 혹은 그 전에 버젼 역시 명시적으로 링크되어야 한다는 것입니다. 솔라리스10에서 POSIX 쓰레드는 표준 C 라이브러리에 존재 합니다. (libc.so). 또 한가지 알아 둘 점은 솔라리스는 1부터 꾸준히 증가하는 숫자를 쓰레드 ID로 지정 한다는 것입니다. 리눅스는 pthread 구조체의 사용자영역 가상 주소를 사용 합니다(구조체는 내부적으로 쓰레드 라이브러리에서 사용됨).

쓰레드는 두 시스템 모두에서 ps(1) 커맨드 혹은 /proc 파일 시스템을 통해 보여질 수 있습니다. 그림 2는 솔라리스의 ps(1) 커맨드 출력을 나타 내고 그림 3은 리눅스에서의 출력을 나타 냅니다. 여기서 알 수 있는 것은 똑같은 옵션을 주었을때 두 머신에서의 출력은 유사 하다는 것입니다.

사용자 삽입 이미지
그림 2: 솔라리스에서의 ps(1) 커맨드 출력


 
사용자 삽입 이미지
그림 3: 리눅스에서의 ps(1) 커맨드 출력


이 커맨드는 상태, 유저, PID, 부모 PID, LWP ID, LWP의 숫자 (유저 프로세스를 위한 숫자. 이것은 쓰레드의 숫자를 의미함), 스케줄링 클래스, 스케줄링 우선순위, 유저영역 가상 사이즈, 대기 채널, 시작 시간, tty, 수행 시간, 그리고 커맨드등을 보여 줍니다. 리눅스 ADDR 를 리포트 하지 않습니다. 그리고 솔라리스는 커널이 프로세스를 유지하는데 사용하는 proc_t 자료 구조의 (커널) 가상 어드레스를 보여 줍니다. 리눅스는WCHAN 을 심볼로 보여 주고 솔라리스는 그것을 주소로 보여 줍니다. 솔라리스에서 WCHAN 컬럼은 쓰레드가 블록됐을때 동기화 변수의 주소를 나타 냅니다. 리눅스에서 WCHAN 은 쓰레드가 sleep 상태 일때의 루틴을 나타냅니다. 솔라리스에서 똑같은 정보를 얻기 위해서는 mdb -k에서::threadlist -v 를 입력 합니다.

알아둘점은 64-비트 커널이 돌아가는 머신(SPARC 혹은 AMD64 아키텍쳐) 에서 ADDR 과 WCHAN 필드는 물음표를 나타낼 것입니다. 이 두 변수의 값을 보기 위해서는 ps -e -o addr,wchan,comm 을 입력합니다.

보통 사용자는 어플리케이션 쓰레드가 무엇을 하는지에 대해 관심이 많을 것입니다. 이럴때에는 pstack(1) 을 이용 합니다. 리눅스에서는pstack 을 이용합니다. 그러나 반드시 이것은 다운로드 받아져야 합니다. http://rpmfind.net/linux/RPM/ 를 참조 바랍니다. 주의할 점은 이것은 오직 하나의 쓰레드의 백트레이스 스택 많을 제공 한다는 것입니다(쓰레드 ID가 인수의 하나로 넘겨짐) 만약 모든 쓰레드의 백트레이스를 원한다면 쓰레드 ID를 하나의 분리된 인수로 넘겨 주어야 합니다.

 <-- get user-level stack(s) of a process on Solaris -->
bash-3.00$ pstack `pgrep mozilla-bin` 
21528: /usr/sfw/bin/../lib/mozilla/mozilla-bin -UILocale en-US
-----------------  lwp# 1 / thread# 1  --------------------
 fef68967 pollsys  (896dac8, 9, 0, 0)
 fef2b2aa poll     (896dac8, 9, ffffffff) + 52
 fe793242 g_main_context_iterate () + 39d
-----------------  lwp# 2 / thread# 2  --------------------
 fef68967 pollsys  (fbf5bd04, 1, 0, 0)
 fef2b2aa poll     (fbf5bd04, 1, ffffffff) + 52
 fede047d _pr_poll_with_poll (816fa0c, 1, ffffffff, fbf5bf64,
                                 fc0558aa, 816fa0c) + 2d5
 fede05f1 PR_Poll  (816fa0c, 1, ffffffff) + 11
 fc0558aa __1cYnsSocketTransportServiceEPoll6M_i_ (816f6b8) + 58
 fc055f7d __1cYnsSocketTransportServiceDRun6M_I_ (816f6b8) + 18f
 fc3d1262 __1cInsThreadEMain6Fpv_v_ (816eb60) + 32
 fede1693 _pt_root (816fcc0) + 9e
 fef67b30 _thr_setup (feec2400) + 51
 fef67f40 _lwp_start (feec2400, 0, 0, 0, 0, 0)
-----------------  lwp# 4 / thread# 4  --------------------
 fef67f7b lwp_park (0, fa87deb8, 0)
 fef620bb cond_wait_queue (825cfec, 816b8d0, fa87deb8, 0) + 3e
 fef62462 cond_wait_common (825cfec, 816b8d0, fa87deb8) + 1e9
 fef62691 _cond_timedwait (825cfec, 816b8d0, fa87df38) + 4a
 fef62722 cond_timedwait (825cfec, 816b8d0, fa87df38) + 27
 fef62761 pthread_cond_timedwait (825cfec, 816b8d0,
                                    fa87df38) + 21
 feddc598 pt_TimedWait (825cfec, 816b8d0, f1c) + b8
 feddc767 PR_WaitCondVar (825cfe8, f1c) + 64
 fc3d417e __1cLTimerThreadDRun6M_I_ (81e5108) + 16e
 fc3d1262 __1cInsThreadEMain6Fpv_v_ (820d690) + 32
 fede1693 _pt_root (820e6b0) + 9e
 fef67b30 _thr_setup (fb520400) + 51
 fef67f40 _lwp_start (fb520400, 0, 0, 0, 0, 0)
bash-3.00$ 

리눅스에서의 동일한 결과가 아래에 있습니다. 흥미로운 것은 Mozilla나 xemacs 같은 프로그램은 리눅스에서 strip 되 있고 솔라리스에서는 strip 되있지 않습니다.

max@linux:~> cd /proc/`pgrep mozilla`/task
max@linux:/proc/3991/task> pstack *

3991: /opt/mozilla/lib/mozilla-bin
(No symbols found)
0xffffe410: ???? (8803488, 8, ffffffff, 8803488, 9, 400fbea0) + 40
0x404b0a6d: ???? (8129258, 4035236c, 57f, 4011e4e6, 4048de14,
                   403513c4) + 20
0x404b0d07: ???? (814b898, 814b898, 0, 0, 415a8f64, 814b898) + 30
0x401dc11f: ???? (8106350, bfffee80, bfffede8, 807673e, 8084cf4, 0)
0x415c4006: ???? (8106350, 0)
0x414fbae4: ???? (8105ee8, 0, 8079c2c, bfffee90, 80a67b8,
                      40ad841c) + 1f0
0x08059b7c: ???? (80e7f08, bffff058, 40017068, 14, 4081ccf8,
                       1) + 90
0x08055a47: ???? (1, bffff134, bffff13c, 4081ccf8, 406eebd0,
                     400168c0) + 40
0x405f2500: ???? (8055840, 1, bffff134, 80557b0, 8055740, 
                     4000d330) + 40000ed8

4001: /opt/mozilla/lib/mozilla-bin
(No symbols found)
0xffffe410: ???? (413eb7f0, 1, ffffffff, 18, 413eb7f8, 0) + 230
0x400c7439: ???? (818911c, 1, ffffffff, 40c5a0a8, ffffffff,
                   8188dec)
0x40bc8a52: ???? (8188dc8, 8188df4, 1, 8188dec, 8188f7c, 1) + 10
0x40bc8bcb: ???? (8188dc8, 413ebbb0, 40102ce0, 400d5238,
                   8189478, 0)
0x40a8da6b: ???? (81893f8, 8189478, 4000ca40, 40102be8, 0, 0)
0x400cb7a6: ???? (8189478, 413ebac4, 0, 0, 0, 0) + 54
0x400fa9dd: ???? (413ebbb0, 0, 0, 0, 0, 0) + bec144d4

4004: /opt/mozilla/lib/mozilla-bin
(No symbols found)
0xffffe410: ???? (40656756, 400d5238, 81ed160, 81ed2d0, 41ffba08,
                   400c5721) + 170fd55
crawl: Input/output error
Error tracing through process 4004
0x1afcdbf8: ????max@linux:/proc/3991/task> 

솔라리스 쓰레드는 기본적으로 유저 스택 사이즈를 1MB로 지정 합니다. 리눅스에서는 2MB 입니다.(수세 9.1)

동기화

두 운영체제는 POSIX 동기화 메카니즘을 지원합니다(예: 뮤텍스, 조건 변수, 읽기/쓰기 락, 세마포어, 베리어등) 기본적으로 메카니즘은 뮤텍스에 바탕을 두고 있습니다. 솔라리스에서 사용자-레벨의 뮤텍스는 "adaptive" 스핀 락을 사용해서 구현 됩니다. 리눅스에서 메카니즘은 "futex" 혹은 fast user level mutex 입니다. 두 메카니즘 모두 비경합 조건에서 커널에 들어가는 것을 회피 하고 반드시 비교할수 있을 만한 성능과 동작을 제공해야 합니다.

솔라리스 사용자-레벨의 "adaptive" 스핀 뮤텍스는 Multithreading in the the Solaris Operating Environment (pdf) 에 자세한 설명이 있습니다. 리눅스의 futex는 Futexes Are Tricky (pdf) 에 자세한 설명이 있습니다.

솔라리스 메카니즘인 lwp_park() 와 lwp_unpark() 그리고 리눅스 메카니즘인 futex_up() 와 futex_down() 은 어플리케이션에서 사용될 수 있습니다. 그러나 필자는 어떠한 소스 코드 예제도 찾지 못했습니다. 아마도 POSIX API들을 사용 하는 것이 가장 좋을 것입니다. 만약 POSIX 라킹 메카니즘에 상대적인 속도 차이를 비교하고자 한다면 (혹은 다양한 다른 라이브러리 루틴, 시스템 콜의 성능 같은) 필자는 libmicro 마이크로 벤치마크의 카피를 얻을 것을 관장하고 그것을 솔라리스와 리눅스에서 사용해 보길 권장 합니다. (libmicro는 여기 에서 다운로드 받으실 수 있습니다)

메모리 관리

커널의 메모리 다루는 방법에 대한 차이를 설명하는 대신 필자는 유저 레벨에서 몇가지 다른 메모리 할당 라이브러리가 존재 하고 대부분 두 OS 에서 사용 할 수 있음 만을 밝히려고 합니다. 유저레벨 메모리 할당자의 비교는 SDN의 A Comparison of Memory Allocators in Multiprocessors 에서 찾아 보실 수 있습니다. http://gee.cs.oswego.edu/dl/html/malloc.html 에 "A Memory Allocator" 는 리눅스에서 사용되는 (조금 지난) 메모리 할당자들에 대한 설명을 포함 하고 있습니다. 또한 소스 코드에서도 관련 커멘트를 찾아 볼 수 있습니다.

타이머

어플리케이션 레벨에서 솔라리스와 리눅스는 둘다 timer_create()timer_delete(), 그리고 nanosleep() 를 포함한 POSIX 타이머 루틴을 제공합니다. 솔라리스는 추가적으로 CLOCK_HIGHRES 라는 타이머를 제공하며 이것은 최적의 하드웨어 소스를 사용하도록 시도 하고 nano 초 수준의 정밀함을 제공 할 것입니다. CLOCK_HIGH_RES 타이머는 아마 미슷한 수준의 정밀성을 리눅스에서 제공할 것입니다. 그러나 커널 패치의 일부로 설치 되어야 합니다.(http://high-res-timers.sourceforge.net/ 에서 고 정밀도 타이머 프로젝트의 홈페이지를 찾으실 수 있습니다). 다음의 예제 코드는 CLOCK_HIGHRES 타이머를 사용하여 유저가 지정한 간격, 유저가 지정한 시간 동안 타이머가 동작 하도록 합니다. 간격은 nano 초 수준으로 지정 되고 동작 시간은 초 수준으로 지정 됩니다. 프로그램이 실행을 완료 하면 타이머가 몇번 동작 했는지와 타이머가 "overrun" 했는지 숫자로 보여 줍니다. 여기서 "overrun" 값이란 타이머가 처음 동작한 시간(시그널이 생성되도록 함)과 시그널이 처리되는 시간 (timer_getoverrun(3RT) 참조) 과의 차이를 의미 합니다. 간격을 너무 짧게 줄 경우 시스템이 정지해 버릴 수도 있습니다.

#include <pthread.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>

#include <errno.h>

#define DURATION 120    /* default time to run in seconds */

  /* default .5 seconds in nanosecs */
#define INTERVAL (1000*1000*500)

void* timer_fcn(void* arg);
void* signaler_thd(void* arg);

/* Program globals */
extern int errno;
int duration = DURATION;
int interval = INTERVAL;

int
main(int argc, char *argv[]) 
{
   sigset_t mask;
   pthread_t wtid = 0;
   pthread_t stid = 0;
   int rval;
   int n;

   if (argc >=2) {
       errno = 0;
       if (argc == 2)
         duration = strtol(argv[1], NULL, 0);
       else if (argc == 3) {
         interval = strtol(argv[1], NULL, 0);
         duration = strtol(argv[2], NULL, 0);
       }
       if (errno || argc > 3 || interval <= 0
          || duration <= 0) {
           fprintf(stderr, "Usage: %s [[interval] duration]\n",
                  argv[0]);
           fprintf(stderr, "interval nsecs, duration seconds\n");
           exit(1);
       }
   }
     
   /* mask SIGALRM signals */
   sigemptyset(&mask);
   sigaddset(&mask, SIGALRM);
   sigaddset(&mask, SIGUSR1);
   rval = pthread_sigmask(SIG_BLOCK, &mask, NULL);
   if(rval != 0) {
      printf("%s: pthread_sigmask failed, errno = %d.\n",
             argv[0], rval);
      exit(1);
   }

   rval = pthread_create(&wtid, NULL, timer_fcn, NULL);
   if (rval != 0) {  /* Waiter create call create failed */
    perror ("Waiter create");
    printf ("Waiter create call failed: %d.\n", rval);
    exit (1);
    }


   /* Do signaler thread */
   rval = pthread_create(&stid, NULL, signaler_thd, &mask);
   if (rval != 0) {  /* Signaler call create failed */
    printf ("Signaler call create failed: %d.\n", rval);
    exit (1);
   }

   /* Wait for waiter and signaler to finish */    
   rval = pthread_join(stid, NULL);
   if (rval != 0) {  /* Signaler call join failed */
    printf ("Signaler call join failed: %d.\n", rval);
    exit (1);
   }

   rval = pthread_join(wtid, NULL);
   if (rval != 0) {  /* Waiter call join failed */
    printf ("Waiter call join failed: %d.\n", rval);
    exit (1);
   }

   printf("done\n");
   exit(0);
}

pthread_mutex_t mp;
pthread_cond_t cv;
int time_expired = 0;
int timerentered;
int timeroverrun;
timer_t itimerid;

void *
timer_fcn(void *arg)
{
  struct itimerspec value;
  struct sigevent event;

  value.it_interval.tv_sec = 0;
  value.it_interval.tv_nsec = interval;  /* nsec intervals  */
  value.it_value.tv_sec = 1;  /* starting in 1 second */
  value.it_value.tv_nsec = 0;  /* plus 0 nanosecs */

  event.sigev_notify = SIGEV_SIGNAL;
  event.sigev_signo = SIGALRM;
  event.sigev_value.sival_int = 0;
  

  if (timer_create(CLOCK_HIGHRES, &event,
     &itimerid) == -1) {
       perror("timer_create failed");
       exit(1);
  }

  /* the second arg can be set to TIMER_ABSTIME */
  if (timer_settime(itimerid, 0, &value, NULL) == -1) {
      /* else time value is relative to when the call is made */
    perror("timer_settime failed");
    exit(1);
  }

  pthread_mutex_lock(&mp);
  while (time_expired == 0)
    pthread_cond_wait(&cv, &mp);
  printf("timerentered = %d\n", timerentered);
  printf("timeroverrun = %d\n", timeroverrun);
  pthread_mutex_unlock(&mp);
  exit(0);
}

int timerset;

void *
signaler_thd(void *arg)
{
    int signo;
    
    while (1) {
      signo = sigwait(arg);
      if (signo == SIGALRM) {
       if (!timerset) {
        struct itimerspec value;
        struct sigevent event;

        timer_t endtimerid;

        ++timerset;
        value.it_interval.tv_sec = 0;
        value.it_interval.tv_nsec = 0;
        value.it_value.tv_sec = duration; /*wait duration secs*/
        value.it_value.tv_nsec = 0;  /* plus 0 nanosecs */

        event.sigev_notify = SIGEV_SIGNAL;
        event.sigev_signo = SIGUSR1;
        event.sigev_value.sival_int = 0;
  

        if (timer_create(CLOCK_HIGHRES, &event,
         &endtimerid) == -1) {
           perror("timer_create failed");
           exit(1);
        }

        /* the second arg can be set to TIMER_ABSTIME */
        if (timer_settime(endtimerid, 0, &value, NULL)
          == -1) {
          perror("timer_settime failed");
          exit(1);
        }
       } else {  /* if (!timerset) */
        ++timerentered;
        timeroverrun += timer_getoverrun(itimerid);
       }
      } else {  /* SIGUSR1 */

       struct itimerspec value;
       struct sigevent event;


       /* cancel the interval timer */
       value.it_interval.tv_sec = 0;
       value.it_interval.tv_nsec = 0;  /* nanosecond intervals */
       /* setting the following to 0 should stop the timer */
       value.it_value.tv_sec = 0;
       value.it_value.tv_nsec = 0;  /* plus 0 nanosecs */

       event.sigev_notify = SIGEV_SIGNAL;
       event.sigev_signo = SIGALRM;
       event.sigev_value.sival_int = 0;
  
       pthread_mutex_lock(&mp);
       if (timer_settime(itimerid, 0, &value, NULL) == -1) {
        perror("timer_settime failed");
        exit(1);
       }

       ++time_expired;
       pthread_cond_signal(&cv);
       pthread_mutex_unlock(&mp);
      }
   }
}

컴파일 된 코드의 실행 예가 있습니다.

  <-- realtime library and best optimization -->
bash-3.00$ cc timerex1.c -lrt -o timerex1 -O -fast
bash-3.00$ ./timerex1  <-- only root can use high res timer
timer_create failed: Not owner
bash-3.00$ su
Password: 
  <-- default interval is .5 seconds, duration is 120 seconds -->
# ./timerex1  
timerentered = 240  <-- timer fired every .5 seconds
timeroverrun = 0
# ./timerex1 1000000 10  <-- interval is 1 msec for 10 secs
timerentered = 9912
timeroverrun = 88
# priocntl -e -c RT ./timerex1 1000000 10  <-- run it real time
timerentered = 10000  <-- timer fired once each msec for 10 secs
timeroverrun = 0
# ./timerex1 100000 10  <-- interval is 100 usecs for 10 seconds
timerentered = 99615  <-- we missed a few
timeroverrun = 386
# priocntl -e -c RT ./timerex1 100000 10  <-- try real time 
timerentered = 99871  <-- almost 1 every 100 microseconds
timeroverrun = 129
# ./timerex1 10000 10  <-- interval is 10 microseconds
timerentered = 485905  <-- here we miss over half
timeroverrun = 514125  <-- (sig handler takes > 10 usecs?)
 <-- using RT 1 usec interval causes hang on my machine -->

# priocntl -e -c RT ./timerex1 1000 10 

IPC

솔라리스와 리눅스 둘다 시스템 V IPC (공유 메모리, 메세지 큐, 세마포어)를 지원 합니다. 두 시스템 모두 파이프와 실 시간 공유 메모리 작업(shm_open()shm_unlink(), 등등)을 지원합니다. 두 시스템 모두 tmpfs 파일 시스템을 지원합니다(파일을 위해 메모리와 스왑 공간을 사용) 솔라리스는 tmpfs 내에 /tmp/var/run, 그리고 /etc/svc/volatile 을 위치 시킵니다 리눅스는 /dev/shm 을 사용합니다. 두 시스템 모두 다른 마운트 지점을 추가 하는 것을 허용 합니다.

솔라리스에서 tmpfs 를 사용 하는 과정이 설명 되어 있습니다; 리눅스를 위한 과정은 아래에 설명 됩니다. 알아둘 점은 솔라리스의 "swap"은 메모리와 디스크를 둘다 사용 합니다. (디스크는 필요시에만) 다시 말해서 /tmp 파일에 생성되는 파일은 메모리에 저장 됩니다. 메모리가 꽉 찰 경우 pageout 데몬이 /tmp 에서 데이타를 디스크에 스왑 스페이스 옮기게 됩니다.

# mkdir /foo
<-- create a tmpfs file system using swap on /foo
# mount -F tmpfs swap /foo  
# df -h /foo
Filesystem         size   used  avail capacity  Mounted on
swap           652M     0K   652M     0%    /foo
# df -h /tmp
Filesystem         size   used  avail capacity  Mounted on
swap           652M    52K   652M     1%    /tmp
# 

아래에 리눅스상에서 비슷한 과정을 설명합니다.

linux:/home/max # mkdir /foo
 <-- tmpfs also uses swap space and memory -->
linux:/home/max # mount tmpfs /foo -t tmpfs 
linux:/home/max # df -h /foo
Filesystem        Size  Used Avail Use% Mounted on
tmpfs         248M     0  248M   0% /foo
linux:/home/max # df -h /dev/shm
Filesystem        Size  Used Avail Use% Mounted on
tmpfs         248M   16K  248M   1% /dev/shm
linux:/home/max # 

이 글의 앞부분에서 언급한대로 libmicro 벤치마크를 수행하여 두 시스템의 상대적인 성능을 비교해 보는 것은 꽤 흥미로울 것입니다.

시그널 핸들링

솔라리스와 리눅스는 시그널을 비슷하게 처리 합니다. 솔라리스에 존재하는 몇몇 시그널은 리눅스에 존재 하지 않고 그 반대도 마찬가지 ?니다. 또한 몇몇 시그널은 다른 시그널 번호를 사용 합니다. 두가지 모두 signal() 위에 sigaction(2) 을 사용하여 시그널을 캐치 하고 멀티 쓰레드 어플리케이션에서 sigwait() 를 사용 하여 비동기 시그널을 처리 합니다. 리눅스에서 sigwait(3) 메뉴얼 페이지는 BUGS 섹션을 가지고 있습니다. 리눅스 시그널 핸들링은 POSIX 표준과는 다릅니다. POSIX에서 비동기 적으로 전달 되는 시그널(프로세스외 외부적으로 전달 되는 시그널)은 현재 블럭된 시그널을 가지고 있지 않는 모든 쓰레드에 의해 처리 됩니다. 리눅스에서 비동기 시그널은 어떤 특정한 쓰레드(시그널은 kill(1)을 통해 특정 쓰레드로 전달될 수 있음)에 전달 됩니다. 솔라리스는 이 경우 POSIX 표준을 구현 합니다. 즉 프로세스의 특정한 쓰레드에 직접적으로 시그널을 전달 할 수 있는 방법은 없습니다. 하나의 방법으로는 프로세스에 특정한 쓰레드로가 아니라 kill(1) 을 통해 프로세스로 전달 하는 것이 있습니다.

http://lsbbook.gforge.freestandards.org/sig-handling.html 에 있는 "Building Applications with the Linux Standard Base"는 몇가지 이러한 차이점들을 기술 하고 있습니다. 주의할 점은 이 페이지는 완벽하게 정확하지는 않다는 것입니다. 예를 들어 이 페이지에서 리눅스에서는 "버스 에러"가 없다는 것을 나타내기 위해 SIGBUS 를 SIGUNUSED 로 지정한다고 설명되어 있습니다. 리눅스의 mmap(2) 맨 페이지에서 설명하고 있는 바로는 mmap 이 사용 되었단 파일에서의 올바른 위치와 부응하지 않는 메모리 범위를 엑세스 할때 SIGBUS 를 받게 된다고 되어 있습니다. (솔라리스도 똑같이 동작함).

솔라리스와 리눅스 두 OS 모두 시그널은 커널에서 유저 모드로 쓰레드가 돌아갈때 pending 된 시그널이 발견 되었을 시에 처리 됩니다. 두 시스템 모두 SIGKILL 과 SIGSTOP 은 다른 시그널에 비교해서 우선순위가 높습니다. 그렇지 않다면 솔라리스에서 시그널은 문서화되지 않은 순서대로 처리 됩니다(낮은 시그널 숫자가 먼저 처리 됨). 리눅스에서 시그널은 전달된 순서대로 처리 됩니다. (SIGKILL 과 SIGSTOP 은 예외).

솔라리스에서 수행중인 프로세스의 시그널 설정을 보기 위해서는 psig 를 사용합니다.

bash-3.00$ psig $$  <-- signal disp for current shell
954:	/usr/bin/bash -i
HUP	caught	termination_unwind_protect	0	HUP,INT,
  ILL,TRAP,ABRT,EMT,FPE,BUS,SEGV,SYS,PIPE,ALRM,TERM,USR1,USR2,
  VTALRM,XCPU,XFSZ,LOST
INT	caught	sigint_sighandler	0
QUIT	ignored
ILL	caught	termination_unwind_protect	0	HUP,INT,
  ILL,TRAP,ABRT,EMT,FPE,BUS,SEGV,SYS,PIPE,ALRM,TERM,USR1,USR2,
  VTALRM,XCPU,XFSZ,LOST
TRAP	caught	termination_unwind_protect	0	HUP,INT,
  ILL,TRAP,ABRT,EMT,FPE,BUS,SEGV,SYS,PIPE,ALRM,TERM,USR1,USR2,
  VTALRM,XCPU,XFSZ,LOST
ABRT	caught	termination_unwind_protect	0	HUP,INT,
  ILL,TRAP,ABRT,EMT,FPE,BUS,SEGV,SYS,PIPE,ALRM,TERM,USR1,USR2,
  VTALRM,XCPU,XFSZ,LOST
EMT	caught	termination_unwind_protect	0	HUP,INT,
  ILL,TRAP,ABRT,EMT,FPE,BUS,SEGV,SYS,PIPE,ALRM,TERM,USR1,USR2,
  VTALRM,XCPU,XFSZ,LOST
FPE	caught	termination_unwind_protect	0	HUP,INT,
  ILL,TRAP,ABRT,EMT,FPE,BUS,SEGV,SYS,PIPE,ALRM,TERM,USR1,USR2,
  VTALRM,XCPU,XFSZ,LOST
KILL	default
BUS	caught	termination_unwind_protect	0	HUP,INT,
  ILL,TRAP,ABRT,EMT,FPE,BUS,SEGV,SYS,PIPE,ALRM,TERM,USR1,USR2,
  VTALRM,XCPU,XFSZ,LOST
SEGV	caught	termination_unwind_protect	0	HUP,INT,
  ILL,TRAP,ABRT,EMT,FPE,BUS,SEGV,SYS,PIPE,ALRM,TERM,USR1,USR2,
  VTALRM,XCPU,XFSZ,LOST
SYS	caught	termination_unwind_protect	0	HUP,INT,
  ILL,TRAP,ABRT,EMT,FPE,BUS,SEGV,SYS,PIPE,ALRM,TERM,USR1,USR2,
  VTALRM,XCPU,XFSZ,LOST
PIPE	caught	termination_unwind_protect	0	HUP,INT,
  ILL,TRAP,ABRT,EMT,FPE,BUS,SEGV,SYS,PIPE,ALRM,TERM,USR1,USR2,
  VTALRM,XCPU,XFSZ,LOST
ALRM	caught	termination_unwind_protect	0	HUP,INT,
  ILL,TRAP,ABRT,EMT,FPE,BUS,SEGV,SYS,PIPE,ALRM,TERM,USR1,USR2,
  VTALRM,XCPU,XFSZ,LOST
TERM	ignored
USR1	caught	termination_unwind_protect	0	HUP,INT,
  ILL,TRAP,ABRT,EMT,FPE,BUS,SEGV,SYS,PIPE,ALRM,TERM,USR1,USR2,
  VTALRM,XCPU,XFSZ,LOST
USR2	caught	termination_unwind_protect	0	HUP,INT,
  ILL,TRAP,ABRT,EMT,FPE,BUS,SEGV,SYS,PIPE,ALRM,TERM,USR1,USR2,
  VTALRM,XCPU,XFSZ,LOST
CLD	blocked,caught	0x807d4d7 	0
PWR	default
WINCH	caught	0x807e182   0  <-- not all syms are present
URG	default
POLL	default
STOP	default
TSTP	ignored
CONT	default
TTIN	ignored
TTOU	ignored
VTALRM	caught	termination_unwind_protect	0	HUP,INT,
  ILL,TRAP,ABRT,EMT,FPE,BUS,SEGV,SYS,PIPE,ALRM,TERM,USR1,USR2,
  VTALRM,XCPU,XFSZ,LOST
PROF	default
XCPU	caught	termination_unwind_protect	0	HUP,INT,
  ILL,TRAP,ABRT,EMT,FPE,BUS,SEGV,SYS,PIPE,ALRM,TERM,USR1,USR2,
  VTALRM,XCPU,XFSZ,LOST
XFSZ	caught	termination_unwind_protect	0	HUP,INT,
  ILL,TRAP,ABRT,EMT,FPE,BUS,SEGV,SYS,PIPE,ALRM,TERM,USR1,USR2,
  VTALRM,XCPU,XFSZ,LOST
WAITING	default
LWP	default
FREEZE	default
THAW	default
CANCEL	default
LOST	caught	termination_unwind_protect	0	HUP,INT,
  ILL,TRAP,ABRT,EMT,FPE,BUS,SEGV,SYS,PIPE,ALRM,TERM,USR1,USR2,
  VTALRM,XCPU,XFSZ,LOST
XRES	default
JVM1	default
JVM2	default
RTMIN	default
RTMIN+1	default
RTMIN+2	default
RTMIN+3	default
RTMAX-3	default
RTMAX-2	default
RTMAX-1	default
RTMAX	default
bash-3.00$ 

필자가 얘기 할 수 있는 것은 리눅스에서 이러한 작업을 쉽게 할 수 있는 방법은 없습니다. 그러나 몇몇 개발자들이 아마 커널 패치/모듈을 구현 해서 개발자가 정보를 얻을 수 있도록 했습니다.

결론

일반적으로 리눅스 혹은 솔라리스에서 개발자가 POSIX-호환의 어플리케이션을 개발 하고 있다면 어플리케인션 반드시 간단한 재컴파일 과정을 통해 각 OS로 포팅 되어야 합니다. 물론 많은 어플리케이션은 POSIX 와 부합하지 않는 부분을 가지고 있습니다. 예를 들어 디바이스ioctl(2) 은 OS에 따라 다르게 되어 있습니다.(그리고 물론 디바이스에 따라)

솔라리스를 위한 문서를 얻는 것은 이성적으로 매우 직관적입니다. 왜냐하면 모든 문서가 http://docs.sun.com에 존재하기 때문입니다. 물론 리눅스를 위한 문서를 얻는 과정도 간단하지만 (웹검색을 통해) 종종 어려울 경우도 있습니다. 개발자는 리눅스가 전형적으로 동일한 작업을 하기 위해 많은 방법이 있음을 발견하게 될 것입니다.(예를 들어 쓰레드의 구현 차이 등) 필자의 개인적은 인상은 리눅스의 문서는 종종 소스 코드 자체가 될 수도 있습니다. 만약 소스 코드에 대한 전체 접근이 가능할 경우 이것은 괜찮은 방법입니다. 개발자는 모든 소스 코드에 대한 접근 권한을 물론 가지고 있지만 이것이 한 장소에 모여 있지는 않습니다. 사실 그것은 여러군데에 퍼져 있는 것처럼 보입니다. 썬의 소스는 현재 (http://www.opensolaris.org) 한군데에 모여 있습니다.

이 글은 두 시스템에서 사용 가능한 툴을 모두 사용하였지만 자세한 부분 까지 파고 들지는 않았습니다. 썬이 오픈솔라리스로 전환하기 전에 리눅스의 발표는 소스 코드에 대한 가시성 면에서 봤을때 항상 어떻게 작업이 돌아 가는지 알 수 있도록 소스 코드가 항상 공개 되있으므로 솔라리스에 비해 한발짝 앞서 있었습니다. 이제는 오픈솔라리스의 DTrace 같은 툴을 통해 리눅스가 솔라리스를 따라와야 하는 상황입니다. 리눅스의 변화 속도를 볼때 필자가 확신 할 수는 없지만 그 시간이 오래 걸리지는 않을 것입니다. 필자는 각 시스템의 서로의 장점과 실수를 배움으로써 상호 보완관계로 발전하기를 바랍니다.

'리눅스 서버에 대해서 > 솔라리스(UNIX) 관련' 카테고리의 다른 글

VM ware 설정하기  (0) 2013.01.15
Resource temporarily unavailable  (0) 2013.01.14
솔라리스에서 파일 첨부  (0) 2013.01.09
UNIX 뮤텍스  (0) 2013.01.09
유닉스 Thread 시스템프로그래밍  (0) 2013.01.09

+ Recent posts