2010년 2월 10일 수요일

iphone programming : Design patterns

2.5. Design Patterns

객체 지향 프로그래밍(Object-Oriented Programming)을 하는 사람들이 항상 고려하는 것 중 하나가 바로 디자인 패턴(Design Pattern) 이라는 것이다. 이 글을 읽는 분들 중에서도 이것에 대해 이미 잘 알고 있는 사람도 많을 것으로 생각된다. 디자인 패턴은 개념적인 것으로서, 프로그램의 구조를 보다 큰 관점에서 보다 유지/보수 관리를 하기 쉽게 하고, 효율성을 높이기 위한 것이다. 디자인 패턴에 대해서는 풍부한 각종 자료를 쉽게 찾아볼 수 있을 것이다. 
Cocoa Fundamentals Guide 문서에서 디자인 패턴에 대한 부분을 참고해도 좋다.

iPhone SDK 위에서 응용 프로그램을 작성하기 위해서는 다음 세 개의 개념이 중심이 된다.
Delegation



우리 말로 '위임' 이라고 말하는데, 대다수의 프로그래밍 용어들이 그렇듯이 이것도 번역한 용어는 어딘가 어색한 느낌을 지울 수 없다. 각설하고, Delegation 은 iPhone SDK 프로그래밍에서 아주 많이 등장하는 패턴이다.
하 나의 객체가 지정된 다른 객체로 주기적으로 메시지를 보내서, 해당 메시지를 처리할 수 있다면 처리할 것을 요구하는 형태를 말한다. 또는, 특정 이벤트가 발생한 경우 그 이벤트를 지정된 다른 객체로 전달하여 처리할 것을 요구하는 것을 말한다.
사실, 프로그램을 작성하는 입장에서 이것은 단순하다. 전달받는 - 혹은 전달하는 - 이벤트일 뿐이다.
우 리가 위임 이벤트를 만나는 것은 Application 이 실행되는 첫 순간부터다. Application 객체는 초기 시작 처리가 다 끝나고나서 applicationDidFinishLaunching: 이라는 이름의 이벤트를 지정된 객체로 전달한다. 첫 시작부터 Delegation 을 만날 수 밖에 없는 것이 iPhone SDK 프로그래밍이다.

Model View Controller



디자인 패턴에 대해 잘 알지 못하는 사람이라도 MVC 에 대해서는 알고 있을 것이다. iPhone SDK 프로그래밍은 MVC 디자인 패턴에 기초한다.
간 단히 짚어보자면, Model 객체는 프로그램이 사용하는 데이터를 의미하며, View 객체는 데이터를 화면에 표시하는 방법이나 사용자가 데이터를 수정하는 방법을 가지고 있는 객체를 의미한다. Interface Builder 는 이 View 를 손쉽게 작성할 수 있도록 도와준다. Controller 객체는 Model 과 View 사이에서 각종 처리를 담당하고 둘 사이를 연결하는 것이다. 쉽게 생각해서 우리가 프로그래밍 언어로 작성하는 프로그램 코드의 대부분은 Controller 가 된다.

Target - Action



만일 화면에 있는 버튼을 손가락으로 누른다면, 이벤트가 다른 오브젝트로 전달된다. 그 이벤트를 받은 오브젝트는 이벤트를 해석하고 그에 알맞는 명령을 수행하게 된다. 이 단순한 메카니즘을 Target-Action 패턴이라고 한다. 단순하지만 앞서 예로 들은 버튼의 경우처럼, 대부분의 iPhone 프로그래밍에서 항상 등장한다.

위의 세 가지 용어는 iPhone SDK 프로그래밍에서는 상식과도 같은 개념이 된다. 모든 것이 세 가지 디자인 패턴 속에서 이루어진다.


2.6. 프로젝트 생성과 기본 코드.

다시 Xcode 를 실행해서, 새로운 프로그램을 작성해 보자. 완전히 새로운 프로젝트를 생성해서 시작해도 좋지만, 앞에서 실행해 본 Hello, iPhone 프로그램을 수정해도 상관 없다. 새로운 프로젝트를 만든다면 마찬가지로 View Based 템플릿을 사용해서 시작하도록 한다.

만일 기본적인 View 조차 없는 프로젝트라면, View 를 생성해서 프로젝트에 포함시키는 작업을 우리가 직접 해주어야 한다. 그러나 하나의 View 가 처음부터 포함되어 있는 템플릿 덕분에 많은 시간을 절약할 수 있다.

필자는 
MyHello 라는 이름의 새로운 프로젝트를 만들었다. 앞으로 편의상 이 이름을 기준으로 각 파일들의 이름을 부르도록 하겠다.

현재, 프로젝트에는 두 개의 XIB 파일이 있다. 여러분이 나중에 각종 프로그램을 작성하다보면 하나의 프로젝트에 여러 개의 XIB 파일이 포함될 것이다.
그렇다면, 프로그램이 처음 실행될 때 어떤 XIB 파일이 처음 사용되는 것일까?

고정된 이름을 가진 XIB 파일이 처음에 사용된다는 식의 원시적인 규칙은 아니다. Workspace 에서 다음 그림과 같이 Resources 항목에 등록된 파일들을 살펴보면, 그곳에 
info.plist 라는 파일이 있다. 이 파일을 선택하면, workspace 에서 그 내용을 보여준다.



info.plist 의 내용에는 작성하는 응용 프로그램의 기본 설정 사항이 저장되어 있으며, 그림에서 보면 아래쪽 항목에 처음 참조하는 XIB 파일이 지정되어 있는 'Main nib file base name'항목도 있다는 것을 볼 수 있다.
즉, iPhone 응용 프로그램이 실행될 때 info.plist 에 있는 각종 초기 설정에 따라 실행 준비가 이루어지며, 처음 사용할 XIB 파일을 선택하는 것도 여기에 있는 설정 값에 따라서 동작하는 것이다.
(여기서도 아직 nib 라는 용어가 xib 로 변경되지 않았다. 점차 모든 용어를 xib로 변경하는 중이니까, 이것도 
나중에 나올 버전의 Xcode 에서는 변경될 것으로 생각된다)

프로젝트가 저장되어 있는 폴더를 Fiinder 에서 직접 열어보면 info.plist 파일이 저장되어 있는 것을 쉽게 찾을 수 있다. 직접 더블 클릭하면 Property List Editor 가 실행되어 Xocde 의 Workspace 에서 보여준 것과 같은 내용을 볼 수 있다. 물로 양쪽 모두 수정도 가능하다. 강제로 텍스트 편집기 같은 에디터에서 이 파일을 열어보면, 단순한 xml 문서 파일임을 알 수 있다.

자, 별로 어렵지 않을 것이다. info.plist 의 자세한 분석은 그리 중요한 것이 아니다. 이제, 프로그램이 실행되면서 property list 에 지정된 XIB 파일이 읽혀진다는 것을 알았다.
그러면, 그 다음 어떤 일이 일어나는가?

MainWindow.xib 항목을 더블 클릭하여, Interface Builder 를 실행해 보자.

IB의 창을 보면, 이전에 보았던 View 의 XIB 파일과는 어딘가 다르게 보인다. IB 의 창에는 아이콘도 두 개 정도 더 있는 것 같고, View 창의 모습도 다르다.




View 창이 재미있다. 실제 iPhone 의 배터리 상태 및 통신 상태 표시칸도 보인다. 그리고 우측 상단에는 작은 휘어진 화살표가 있는데, 이것을 클릭하면 View 창이 실제로 90도 회전한다. 처음 직접 클릭해보면 기대 이상의 동작에 깜짝 놀랄 것이다.

지금 살펴볼 것은 MainWindow.xib 창에 있는 객체 아이콘들이다. 여기에는 처음 보는 아이콘들이 있는데, 그 중에서 MyHello App Delegate 라는 객체가 보인다. 이것을 선택한 후 속성 창에서 connections 탭을 선택해서 그 항목들을 살펴보자.



Referencing Outlets 로 하나의 Outlet 이 연결되어 있는데, delegate 가 File's Owner 와 연결되어 있다.(Outlet 이 뭔지는 나중에 다루자) File's Owner 객체를 선택해서 connections 를 살펴보면, 반대로 delegate 가 MyHello App Delegate 객체와 연결되어 있다는 것을 확인할 수 있다.

앞에서 말한대로, File's Owner 는 Application 자체의 인스턴스 객체를 의미한다. 여기에 delegate 클래스로 MyHello App Delegate 가 등록되어 있는 것이다. 고맙게도, MyHello App Delegate 클래스를 생성하고 그것을 File's Owner 의 delegate 메시지를 받도록 등록되어 있는 이유는 모두 템플릿이 기본적으로 자동 생성해 준 덕분이다.

이렇기 때문에, MyHelloAppDelegate.m 객체로 응용 프로그램의 delegate 이벤트가 전달된다.

이렇게 지정된 규칙에 따라, MyHello 프로그램은 처음 실행되면서 최초의 MainWindow.xib 에 연결된 delegate 연결을 보고서 '위임'메시지를 보내기 시작한다.

그렇게 처음 보내기 시작하는 delegate 메시지는 바로 
"나는 실행준비 다 끝났어. 너할거 있으면 해" 를 의미한다.
그 메시지의 이름은 
applicationDidFinishLaunching: 이다. Xocde 로 돌아와서, MyHelloAppDelegate.m 의 소스 코드를 살펴보자.
다음과 같은 코드가 생성되어 있는 것을 확인할 수 있을 것이다.



- (void)applicationDidFinishLaunching:(UIApplication *)application {    
    
    // Override point for customization after app launch    
    [window addSubview:viewController.view];
    [window makeKeyAndVisible];
}

이것은 Objective-C 프로그래밍 언어의 문법으로 되어 있는 소스 코드다. C++ 와 뭔가 유사성이 있어 보이기도 하는데, C 언어와는 좀 달라보인다.

이 문서에서는 Objetive-C 언어에 대한 상세한 설명은 하지 않는다. 개략적인 내용은 뒤에서 다시 정리하도록 하겠다. Objective-C 언어에 대한 설명은 애플 개발자 사이트에서 충분히 많은 문서를 찾을 수 있을 것이다.

간단히 설명하자면 applicationDidFinishLaunching: 메시지를 받아서 처리하는 코드를 작성한다는 것은, C 언어로 생각할때 applicationDidFinishLaunching: 이라는 이름의 함수를 만드는 것과 같은 말이다. 단, 메시지 전달은 함수 호출과는 달리 동적인 개념이며, 메시지를 받는 객체에서 해당 메시지를 처리할 수 있는 코드가 없다고 해서 컴파일 시 링크에러가 난다거나, 런타임에 에러가 발생하지는 않는다는 점만 명심하자.

위 코드의 내용을 보면, 윈도우에 하나의 view 를 추가하고나서, 그 윈도우를 화면에 표시하도록 해 주는 내용임을 알 수 있다.
이 런 식으로, MyHelloAppDelegate.m 에는 MyApp 프로그램의 Delegation 메시지를 처리하는 코드들이 있으며, 현재는 없지만 필요한 delegate 메시지 처리 코드를 추가할 수도 있다. 이와 함께 헤더 파일도 살펴보면서 코드의 스타일을 짐작해 보자.

프로그램이 종료될 때는 dealloc 메시지를 전달한다. 여기서는 메모리를 해제하는 코드들이 있게 된다. 이처럼 delegate 객체에서는 응용 프로그램이 위임하는 각종 이벤트를 받아서 처리하게 된다.



이제 기본적으로 초기화 과정에 대한 부분들을 살펴보았다. 이제 다른 부분을 한번 살펴보자. 계속 보다시피, 우리가 만든 프로젝트는 기본 템플릿에 의해 하나의 View 를 가지고 있으며, 그것은 이미 Window 화면에 연결되어 있는 상태다.

MVC 디자인 패턴을 떠올려보자. 여기 하나의 View 가 존재하므로, 있어야 할 것은 나머지 Model 과 Controller 이다.

앞서 말할 것 처럼 Model 은 프로그램이 처리할 자료를 말한다. 현재 우리가 작성할 프로그램은 별로 하는일이 없기 때문에 Model 에 대한 부분은 잊어버리자. 그러면 남는 것은 Controller 인데, 이것도 템플릿에 의해 MyHelloViewController.m 이라는 이름으로 해당 View 와 연결된 Controller 코드가 자동 생성되어 프로젝트에 포함되어 있는 것이다. 

MyHelloViewController.xib 를 더블 클릭하여 이것도 Interface Builder 에서 열어보자. Interface Builder 는 이미 열려있는 MainWindow.xib 파일과 동시에 보여줄 수 있다.




앞에서 해봤던 대로 xib 의 내용을 조금 살펴보자. 일단 주목할 것은, 여기에 있는 
File's Owner 는 응용 프로그램 자체가 아니라 Controller 의 인스턴스를 의미하는 객체다.
즉, MyHelloViewController.m 객체의 인스턴스를 의미하는 아이콘이다. 그리고 File's Owner 객체와 View 객체는 서로 View 컨트롤러의 
view Outlet(아웃렛) 에 의해 연결되어 있다. 마찬가지로 connections 창에서 서로의 연결을 확인할 수 있다.


outlet 은 단지 xib 파일에서 아이템을 서로 연결하는데에 사용되는 인스턴스 변수와 같은 것이다. MyHelloViewController 에 이런 인스턴스 변수가 있는 이유는, 모든 View 는 UIViewController 에서 상속되기 때문이다.


템플릿에 의해 제공된 MainWindow 와 View 는 이상 살펴본 형태로 서로 연결되어 구성되어 있다. 조금 더 살펴본다면 어느정도 감은 잡을 수 있을 것으로 생각된다. Objective-C 언어와 각종 용어의 낮설음은 일단 참고 그냥 넘어가보자.
지나보면 별거 아니다!

2010년 2월 2일 화요일

경고: 시계가 잘못되었음이 발견되었습니다.

리눅스에서 GCC로 작업하다가 다른 PC로 옮겨서 make를 실행해 보니 아래와 에러 메시지가 출력되네요.

make 경고make: 경고: 시계가 잘못되었음이 발견되었습니다. 빌드가 불완전할 수 있습니다.

시간이 없어서 급한 마음에 그냥 작업하려 했는데 계속 눈에 거슬립니다. 아마도 이전에 작업했던 PC와 시간 차이가 너서 그러는가 보다 했습니다. 해서 파일을 다른 곳에 옮겼다가 새로 복사해서 make를 실행했지만 똑 같은 경고 메시지가 나오네요.

저는 컴파일 에러 보다 경고(warning)메시지를 더 무서워하기 때문에 방법을 찾아 보았습니다. 그랬더니 이유는 작업 PC의 날짜가 아주 어뚱하게 설정되어 있네요. date 명령으로 날짜와 시간을 바로 잡아 주니 make 가 정상적으로 처리되어 있습니다.

그러나 date로 설정하는 날짜와 시간 입력 모습이 왜 이럴까요? 날짜와 시간을 아래와 같이 입력해 주어야 합니다.

]# date [월][일][시][분][년].[초]

예를 들어서 2007년 10월 16일 오후 10시 51분 19초라면

]# date 101622512007.19[엔터]입니다.

흠~ 좀 복잡해 보이죠. ^^;
date를 실행하시려면 su로 들어 가셔야 합니다.

]$ su - Password: ]# date 2000. 01. 01. (토) 01:01:01 KST ]# date 101622452007.30 2007. 10. 16. (화) 22:45:30 KST !!! 그러나 더 쉬운 방법이 있습니다. 바로 타임 서버를 이용하는 것입니다. !!! ]# rdate -s time.bora.net ]# date 2007. 10. 16. (화) 22:49:47 KST

매우 간단하고 간편합니다. ^^ rdate 도 기억해야 겠네요.

fcntl 레코드잠금


설명
fcntl() 함수는 파일을 전체나 파일의 일부를 다른 프로세스에서 사용하는 것을 제한할 수 있도록 해 주는 함수입니다. 물론 파일을 사용하기 위해 open() 함수나 fopen() 함수의 mode를 이용하여 다른 프로세스가 읽기나 쓰기를 제한할 수 있습니다. 그러나 이것은 파일 전체에 대해 적용되며 제한 내용을 변경하기 위해서는 파일을 닫았다가 다시 열기를 해야 합니다.
fcntl()은 열려진 파일에 대해서 필요에 따라 제한을 여러 번 변경하실 수 있으며, 파일 전체 뿐만 아니라 일부를 제한, 즉 "잠금" 상태를 만들 수 있기 때문에 fcntl() 함수를 "파일 잠금 함수"라기 보다는 "레코드 잠금 함수"로 불리워 집니다.
*** 주의하실 내용은 *** 파일에 대한 잠금은 프로세스별로 잠금 정보를 지정하는 것 뿐이지 실제로 다른 프로세스가 읽고 쓰는 것을 못하게 하는 것은 아닙니다. 즉, 쓰기를 제한했다고 해서 다른 프로세스에서 write() 가 실행이 안 되거나 에러가 발생하지 않습니다.
잠금 정보는 하나의 파일을 여러 프로세스가 동시에 사용하는 경우, 같은 시간에 쓰기를 하거나 아직 한쪽에서 변경 중이라면 다른 프로세스가 읽지를 못하게 하기 위한 정보를 주고 받기 위한 방법으로 이해햐셔야 합니다.
아래 예제에서도 쓰기를 하기 전에 쓰기 잠금이 가능한지를 확인한 후에 write() 를 실행했습니다. 그러나 확인없이 쓰기를 한다면 쓰기가 가능합니다. 그러므로 잠금 정보는 프로세스가 쓰기를 못하게 한다가 아니라 지금은 읽어 서는 안 된다 또는 쓰기를 해서는 안 된다라는 정보로 이용하셔야 합니다.
int fcntl(int fd, int cmd, struct flock * lock);
cmd에는 아래와 같이 상수로 정의되어 있습니다.
cmd의미
F_GETLK레코의 잠금 상태를 구해지며, 정보는 세번째 인수인 lock에 담겨져 옮니다.
F_SETLK레코드 잠금을 요청하며, 다른 프로세스가 먼저 선점해서 실패했다면 즉시 -1 로 복귀합니다.
F_SETLKW끝의 W는 wait의 약자로 레코드 잠금을 요청했는데, 다른 프로세스가 먼저 선점해서 실패했다면 그 프로세스가 해제할 때까지 대기합니다.
이번에는 struct flock * lock의 내용을 알아 봐야 겠지요. ^^
struct flock {
        short   l_type;
        short   l_whence;
        off_t   l_start;
        off_t   l_len;
        pid_t   l_pid;
        __ARCH_FLOCK_PAD
};
l_type 은 어떻게 잠금을 할지, 해제할지를 정합니다. 즉, 아래와 같은 상수가 정의되어 있습니다.
l_type의미
F_RDLCK다른 프로세스가 읽기 잠금만 가능하게하고 쓰기 잠금은 못하게 합니다.
F_WRLCK다른 프로세스는 읽기 잠금과 쓰기 잠금 모두 불가능하도록 합니다.
F_UNLCK잠금을 해제합니다.
l_whence 는 블록할 영역을 지정하는 기준 위치를 지정합니다. 즉, 파일 첫 부분부터 따질지, 아니면 현제 읽기/쓰기 포인터를 기준으로 따질지를 정합니다.
l_whence의미
SEEK_SET파일의 시작 위치
SEEK_CUR현재 읽기/쓰기 포인터를 기준
SEEK_END파일의 끝을 기준
l_start와 l_len은 l_whence가 가르키는 위치에서 블록을 지정합니다. 또한 l_len이 0 의 값이 되면 l_len값은 l_start부터 파일 끝 사이의 크기가 됩니다.
즉,
  • l_whence가 SEEK_CUR이고
  • l_start 가 0이면서
  • l_len 이 0 이면
파일 전체를 가르키게 됩니다. 아래의 그림을 보십시오.
l_pid 는 F_GETLK 를 실행하여 레코드에 대한 잠금 상태 정보를 구할 때, 이미 잠금을 실행하고 있는 프로세스의 ID 입니다.
헤더unistd.h, fcntl.h
형태int fcntl(int fd, int cmd, struct flock * lock);
인수
int fd제어 대상 파일 디스크립터
int cmd제어 동작 명령
struct flock * lock잠금을 위한 옵션
반환
-1실패
-1 !=cmd에 따라 달라짐
예제 1
예제에서는 같은 파일을 부모 프로세스가 열기를 하고 7번째 문자부터 7개의 문자 영역을 읽기만 가능할 뿐 쓰기를 해서는 안된다는 잠금 정보를 설정합니다. 차일드 프로세스는 쓰기를 할 때, 변경하려는 영역이 과연 쓰기가 가능한 것인지를 확인한 후에 쓰기를 합니다.
예제에서 차일드는 2 곳에 따로따로 쓰기를 하는데, 한 번은 쓰기가 가능한 곳이지만 다른 한 곳은 부모 프로세스에 의해 읽기 잠금한 영역입니다. 이 때 쓰기가 어떻게 되는지 보겠습니다.
예제에서 사용하는 test.txt의 내용은 "FORUM.FALINUX.COM HAPPY NEW YEAR!!" 입니다.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
   char    *filename = "./test.txt";
   int      fd;
   pid_t    pid;
   struct   flock filelock;

   pid   = fork();
   switch( pid)
   {
      case -1  :
      {
         printf( "자식 프로세스 생성 실패\n");
         return -1;
      }
      case 0   :
      {
         printf( "자식: 부모 프로세스를 위해 잠시 대기하겠습니다.\n");
         sleep( 1);

         printf( "자식: 파일의 내용을 수정합니다.\n");
         fd = open( filename, O_RDWR ¦ O_CREAT, 0666);

         filelock.l_type   = F_WRLCK;
         filelock.l_whence = SEEK_SET;
         filelock.l_start  = 0;
         filelock.l_len    = 6;

         if ( -1 == fcntl( fd, F_SETLK, &filelock))
         {
            printf( "자식:레코드 잠금에 실패해서 forum을 쓰지를 못했습니다.\n");
         }
         else
         {
            write( fd, "forum", 6);
         }

         filelock.l_type   = F_WRLCK;
         filelock.l_whence = SEEK_SET;
         filelock.l_start  = 6;
         filelock.l_len    = 7;

         if ( -1 == fcntl( fd, F_SETLK, &filelock))
         {
            // 여기서는 이 If 절을 만족하게 됩니다.
            printf( "자식:레코드 잠금에 실패해서 falinux를 쓰지 못했습니다.\n");
         }
         else
         {
            write( fd, "falinux", 7);
         }

         close( fd);
         break;
      }
      default  :
      {
         printf( "부모: 파일을 열고 레코드 잠금하겠습니다.\n");
         fd = open( filename, O_RDWR, 0666);

         filelock.l_type   = F_RDLCK;
         filelock.l_whence = SEEK_SET;
         filelock.l_start  = 7;
         filelock.l_len    = 7;

         if ( -1 != fcntl( fd, F_SETLK, &filelock))
         {
            printf( "부모: 잠금에 성공했으며 3초간 대기합니다.\n");
            sleep( 3);
         }
         close( fd);
      }
   }
}
]$ ./a.out
]$ cat test.txt
FORUM.FALINUX.COM HAPPY NEW YEAR!!          // 모두 대문자
]$ ./a.out
자식: 부모 프로세스를 위해 잠시 대기하겠습니다.
부모: 파일을 열고 레코드 잠금하겠습니다.
부모: 잠금에 성공했으며 3초간 대기합니다.
자식: 파일의 내용을 수정합니다.
자식: 레코드 잠금에 실패해서 falinux를 쓰지 못했습니다.
]$ cat test.txt
forumFALINUX.COM HAPPY NEW YEAR!!          // 역시 첫번째 쓰기만 적용되었습니다.
]$
예제 2
예제 1은 cmd를 F_SETLK를 사용했지만 이번에는 잠금 상태가 해제될 때까지 기다리는 F_SETLKW를 사용해 보겠습니다.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
   char    *filename = "./test.txt";
   int      fd;
   pid_t    pid;
   struct   flock filelock;

   pid   = fork();
   switch( pid)
   {
      case -1  :
      {
         printf( "자식 프로세스 생성 실패\n");
         return -1;
      }
      case 0   :
      {
         printf( "자식: 부모 프로세스를 위해 잠시 대기하겠습니다.\n");
         sleep( 1);

         printf( "자식: 파일의 내용을 수정합니다.\n");
         fd = open( filename, O_RDWR ¦ O_CREAT, 0666);

         filelock.l_type   = F_WRLCK;
         filelock.l_whence = SEEK_SET;
         filelock.l_start  = 0;
         filelock.l_len    = 6;

         if ( -1 == fcntl( fd, F_SETLKW, &filelock))
         {
            printf( "레코드 잠금에 실패해서 forum을 쓰지를 못했습니다.\n");
         }
         else
         {
            write( fd, "forum", 6);
         }

         filelock.l_type   = F_WRLCK;
         filelock.l_whence = SEEK_SET;
         filelock.l_start  = 6;
         filelock.l_len    = 7;

         if ( -1 == fcntl( fd, F_SETLKW, &filelock))
         {
            printf( "레코드 잠금에 실패해서 falinux를 쓰지 못했습니다.\n");
         }
         else
         {
            write( fd, "falinux", 7);
         }

         close( fd);
         printf( "자식: 프로그램을 종료합니다.\n");
         break;
      }
      default  :
      {
         printf( "부모: 파일을 열고 레코드 잠금하겠습니다.\n");
         fd = open( filename, O_RDWR, 0666);

         filelock.l_type   = F_RDLCK;
         filelock.l_whence = SEEK_SET;
         filelock.l_start  = 7;
         filelock.l_len    = 7;

         if ( -1 != fcntl( fd, F_SETLK, &filelock))
         {
            printf( "부모: 잠금에 성공했으며 3초간 대기합니다.\n");
            sleep( 3);
         }
         // 파일이 닫히면 자동으로 잠금이 해제
         close( fd);
      }
   }
}
]$ ./a.out
자식: 부모 프로세스를 위해 잠시 대기하겠습니다.
부모: 파일을 열고 레코드 잠금하겠습니다.
부모: 잠금에 성공했으며 3초간 대기합니다.
자식: 파일의 내용을 수정합니다.
]$ 자식: 프로그램을 종료합니다.

]$ cat test.txt
forumfalinux.COM HAPPY NEW YEAR!!
]$