gcc와 make

2008/01/07 11:21 / Resource
참고문헌

Running LINUX(Matt Welsh, Lar Kaufman), "오렐리 출판사"

1. 시작하면서

2. gcc 강좌

3. make 강좌


1. 시작하면서

1.1 C 와 gcc 와의 관계

세상에서 제일 뛰어난 C 컴파일러 중 하나인 gcc 는 리눅스나 기타 자유 운영체제에 있어 커다란 보배가 아닐 수 없습니다. 우리가 알고 있는 유닉스가 C 언어로 거의 다 만들어졌듯이 리눅스의 모국어는 바로 gcc 입니다.

사실 많은 분들이 리눅스 해커(hacker), 구루(guru)의 경지가 되고 싶어합니다. 그렇게 되길 원하신다면 리눅스용의 모국어인 gcc 를 익히십시요. gcc 를 알면 리눅스를 아는 것이나 다름 없습니다. 사실 C 와 유닉스가 따로 떨어진 것이 아니라 어떻게 보면 일심동체라고 할 수도 있듯이 gcc 와 리눅스는 일심동체라고 봐도 무방합니다.

C 언어! 이는 유닉스와 심지어 마이크로소프트 제품에 이르기까지(어떤 식으로 변질되었든 간에 ) 컴퓨터 세상의 ``만국 공통어''입니다. 여태까지 이러한 언어의 통일을 이뤄낸 것은 C 언어밖에 없습니다. 컴퓨터 언어의 에스페란토어를 지향하는 많은 언어들( 자바, 티클/티케이 )이 나오고 있으나 이는 두고 볼 일입니다. 그리고 그 언어를 구사한다 할 지라도 C 언어는 역시나 ``기초 교양 언어''입니다.

여러분은 리눅스에서 gcc 를 통하여 그 동안 도스/윈도 환경에서 배운 엉터리 C 언어를 잊으셔야 합니다. 감히 말하건데 그것은 C 언어가 아닙니다. C 언어는 만국 공통어야 함에도 불구하고 몇몇 회사들, 도스/윈도와 같은 환경에서 변질되어 각 환경마다 ``새로운 문법''을 배워야 하는 어처구니없는 사태가 벌어졌습니다. 터보 C 와 MS-C를 배우면서 혼란도 많았고 그 뒤에 나온 녀석들은 완전히 다른 놈들이나 다름 없습니다.

지금 리눅스에서 여러분은 C 언어의 ``정통 소림권법''을 익히실 수 있습니다. 기초가 없이 비법만 전수받아 보았자 다른 곳에 가면 수많은 비법을 지닌 무림고수들에게 여지없이 깨지기 마련입니다. 하지만 아무리 괴로와도 처음에 물 길어오는 것, 마당 쓰는 일부터 시작하면 철통같은 신체를 단련하기 때문에 온갖 꽁수 비법으로는 여러분을 헤칠 수 없습니다. 또한 정통 권법을 연마한 사람은 기본기가 갖춰져 있으므로 대련 중에도 상대의 비법을 금방 간파하고 심지어 상대의 비법만마저 자신의 것으로 하기도 합니다. ^^


1.2 gcc 에 대한 이야기 하나

gcc 는 GNU 프로젝트에 의해 만들어진 작품의 하나로서 그 명성은 하늘을 찌를 듯합니다. GNU 프로젝트응의 산물 중 가장 멋진 것을 꼽으라면 저는 주저하지 않고 C 컴파일러의 최고봉인 gcc 를 지목할 것입니다.

실제로 gcc 의 명성은 뛰어나며 수많은 상용 회사도 스폰서를 해주고 있다는 것을 아시는지요? 예를 들어 넥스트사( 지금은 사라짐 )의 새로운 C 언어인 ``오브젝티브 C''는 gcc 를 가져다 만든 것이며 FSF 측에 다시 기증 되었습니다.

gcc 는 아주 강력합니다! 이미 상용 유닉스에 달려오는 AT&T 스타일, BSD 스타일의 C 언어 문법은 물론 ANSI C 를 기본으로 하여 모든 문법을 소화해낼 수 있으며 특유의 문법도 가지고 있습니다. 아주 구식 컴파일러, 아주 구식 문법도 소화해낼 수 있습니다. 이미 많은 사람들이 상용 유닉스에도 gcc 를 설컴치하여 사용하는 경우가 많지요. ( 물론 금전적인 문제가 많이 작용 ^^ )

gcc 는 매우 단순합니다. 어떤 의미로 이런 말을 하는가 하면, 터보 C/볼랜드 C 통합환경이나 윈도 환경의 비주얼한 환경을 제공하지 않는다는 것입니다. -.- 그들이 상당히 오토매틱한 성격을 갖는 반면, gcc 는 오로지 수동 스틱방식입니다. 각각의 장단점이 있지만 여러분이 일단 gcc 를 만나시려면 각오는 하고 계셔야 합니다. 도스/윈도에서 보던 것을 원하지 마십시요. gcc 는 껍데기에 신경쓸 겨를조차 없습니다. gcc 는 오로지 명령행 방식만을 제공합니다. 그리고 그 자체로 파워풀합니다. 개발 방향은 계속 ``뛰어난 능력''이지 겉모양 화장은 아닐 것입니다. ( 만약 겉모양을 원하신다면 그것은 여러분의 몫입니다. xwpe 같은 것이 그 좋은 예라고 할 수 있습니다 )

gcc 는 어떻게 보면 C 언어에 대한 개념이 서지 않는 사람에게는 무리인 C 컴파일러인 듯 합니다. 기초 지식없이 사용한다는 것은 불가능에 가깝습니다. 하지만 C 언어를 확실하게 기초부터 배워서 어디서든 쓰러지지 않는 무림고수가 되기 위해서는 gcc 를 권합니다. 자잘한 무공을 하는 깡패가 되느냐? 아니면 정신을 지닌 무림고수가 되느냐?는 여러분의 선택에 달렸습니다.

gcc 가 어렵기만 한가? 하면 그렇지는 않습니다. gcc 는 상당한 매력을 지니고 있습니다. 그 매력으로 인해 한 번 빠지면 다른 컴파일러가 상당히 우습게 보이기까지 합니다. ( 그렇다고 다른 컴파일러를 비웃지는 마세요 ^^ 그거 쓰는 사람들이 자존심 상해서 엄청 화를 낼 테니까요. 개인적으로 생각하기에 gcc 에 대적할 수 있을 정도되는 컴파일러는 와콤 C 컴파일러 정도? )

gcc 를 배우시려면 정신 무장(?)이 중요하다고 생각해서 이렇게 장황하게 읊었습니다. 심플하게 배우면서 여러분의 리눅스, C 컴파일러에 대한 두려움을 하나씩 없애고 C 언어 위에 군림하시기 바랍니다.

자, 이제는 잡담없이 시작합니다.

2. gcc 강좌

2.1 gcc 에 대한 기본 이해

명령행 상태에서 다음과 같이 입력해봅시다. 여러분이 사용하같고 있는 gcc 버전은 알아두고 시작하셔야겠죠?

 [yong@redyong yong]$ gcc -v
 Reading specs from /usr/lib/gcc-lib/i386-linux/2.7.2.1/specs
 gcc version 2.7.2.1
 [yong@redyong yong]$ 

gcc -v 이라고 입력하니까 ``Reading specs from..'' 이라같고 말하면서 그 결과값을 ``gcc version 2.7.2.1''이라고 말해주고 있습니다. 자, 어디서 gcc 에 대한 정보를 읽어오는지 봅시다.

  /usr/lib/gcc-lib/i386-linux/2.7.2.1/specs

gcc 를 여러분이 소스를 가져다 손수 설치해보신 적은 없을 것입니다. 보통은 바이너리 패키지로 된 것을 가져다 설치하지요. 나중에 정말 휴일에 너무 심심하다 싶으면 gcc 의 소스를 가져와서 컴파일해보십시요. 참, 재미있는 경험이 될 것입니다. 이미 여러분이 갖고 있는 gcc 를 가지고 새로운 gcc 를 컴파일하여 사용합니다. C 컴파일러를 가지고 새 버전의 C 컴파일러를 컴파일하여 사용한다! 이런 재미있는 경험을 또 어디서 해보겠습니까?

gcc 패키지가 어떤 것으로 구성되어 있는지.. gcc 가 제대로 설치되어 있는지 알아보면 좋겠죠?

다음과 같습니다.

 /lib/cpp       -----------> /usr/lib/gcc-lib/i386-linux/2.7.2.1/cpp ( 링크임 )
 /usr/bin/cc    -----------> gcc ( 링크임 )
 /usr/bin/gcc                C 컴파일러 ``front-end''
 /usr/bin/protoize
 /usr/bin/unprotoize
 /usr/info/cpp.info-*.gz     GNU info 시스템을 이용하는 화일들
 /usr/info/gcc.info-*.gz                        
 /usr/lib/gcc-lib

마지막 /usr/lib/gcc-lib 디렉토리에 아래에 gcc 에 관한 모든 내용이 설치됩니다.

보통 다음과 같은 디렉토리 구조를 가집니다.

        /usr/lib/gcc-lib/<플랫폼>/< gcc 버전 >

보통 우리는 리눅스를 i386 ( 인텔 환경 )에서 사용하고 있으므로 다음과 같이 나타날 것입니다.

        /usr/lib/gcc-lib/i386-linux/2.7.2.1

( i386-linux, i486-linux, i586-linux 는 각기 다를 수 있습니다. 하지만 상관없는 내용입니다. 미친 척 하고 다른 이름을 부여할 수도 있습니다. )

그럼 계속 해서 /usr/lib/gcc-lib 밑의 내용을 살펴보죠.

 /usr/lib/gcc-lib/i386-linux/2.7.2.1/cc1
 /usr/lib/gcc-lib/i386-linux/2.7.2.1/cpp
 /usr/lib/gcc-lib/i386-linux/2.7.2.1/include/*.h
 /usr/lib/gcc-lib/i386-linux/2.7.2.1/libgcc.a
 /usr/lib/gcc-lib/i386-linux/2.7.2.1/specs

cc1이 진짜 C 컴파일러 본체입니다. gcc 는 단지 적절하게 C 인가, C++ 인가 아니면 오브젝티브 C 인가를 검사하고 컴파일 작업만이 아니라 ``링크''라는 작업까지 하여 C 언어로 프로그램 소스를 만든 다음, 프로그램 바이너리가 만들어지기까지의 모든 과정을 관장해주는 ``조정자'' 역할을 할 뿐입니다.

C 컴파일러는 cc1, C++ 컴파일러는 cc1plus, 오브젝티브 C 컴파일러는 cc1obj 입니다. 여러분이 C++/오브젝티브 C 컴파일러를 설치하셨다면 cc1plus, cc1obj 라는 실행화일도 찾아보실 수 있을 겁니다. cpp 는 "프리프로세서"입니다. #include 등의 문장을 본격적인 cc1 컴파일에 들어 가기에 앞서 먼저(pre) 처리(process)해주는 녀석입니다.

참고로 g++ 즉 C++ 컴파일러( 정확히는 C++ 컴파일러 프론트 엔드 )에 대한 패키지는 다음과 같습니다.

 /usr/bin/c++   --------------------------->    g++ 에 대한 링크에 불과함
 /usr/bin/g++
 /usr/lib/gcc-lib/i386-linux/2.7.2.1/cc1plus    ( 진짜 C++ 컴파일러 )

오브젝티브 C 컴파일러 패키지는 다음과 같습니다.

 /usr/lib/gcc-lib/i386-linux/2.7.2.1/cc1obj
 /usr/lib/gcc-lib/i386-linux/2.7.2.1/include/objc/*.h
 /usr/lib/gcc-lib/i386-linux/2.7.2.1/libobjc.a

구성요소가 어떤 것인지 아셨으니 좀 도움이 되셨을 겁니다.

2.2 gcc 사용하기

hello.c 라는 지긋지긋한 소스 하나를 기준으로 설명합니다 ^^


#include <stdio.h>

int
main ( void )
{
  (void) printf ( "Hello, Linux Girls! =)\n" );
  return 0;
}

참고로 제일 간단한 소스는 다음과 같은 것입니다. ^^


main () {}

컴파일을 해보겠습니다! $ 는 프롬프트이지 입력하는 것이 아닌 것 아시죠?

 $ gcc hello.c
 $

무소식이 희소식이라... gcc <C소스 화일명> 이렇게 실행하고 나서 아무런 메시지도 나오지 않고 다음 줄에 프롬프트만 달랑 떨어지면 그것이 바로 컴파일 성공입니다.

여러분은 무심코 다음과 같이 결과 프로그램을 실행시키려 할 것입니다.

 $ hello
 bash: hello: command not found
 $

예. 땡입니다. ^^

여러분은 다음과 같이 실행시켜야 합니다.

 $ ./a.out

맨 앞의 도트 문자(.)는 현재 디렉토리를 의미합니다. 그 다음 디렉토리 구분 문자 슬래쉬(/)를 쓰고 유닉스 C 에서 ``약속한'' C 컴파일러의 출력 결과 바이너리 화일인 a.out 을 써줍니다.

이러한 습관은 아주 중요합니다. 여러분이 현재 디렉토리에 어떤 실행 화일을 만들고 나서 테스트를 해 보려고 한다면 꼭 ./<실행 화일명> 이라고 적어줍니다.

유닉스는 기본적으로 PATH 라는 환경변수에 있는 디렉토리에서만 실행화일을 찾을 뿐입니다. 만약 PATH 라는 환경변수에 현재 디렉토리를 의미하는 도트 문자(.)가 들어있지 않으면 현재 디렉토리의 실행화일은 절대 실행되지 않습니다. 게다가 현재 디렉토리를 PATH 환경 변수에 넣어준다 할 지라도 도스처렁럼 현재 디렉토리를 먼저 찾는다든지 하는 일은 없습니다. 오로지 PATH 에 지정한 순서대로 수행합니다.

실행 바이너리명이 계속 a.out 으로 나오면 좀 곤란하죠. 뭐 물론 mv 명령으로 a.out 의 이름을 바꾸면 되지만서도...

-o 옵션

-o 옵션( 소문자 o 임! )은 출력(output) 화일명을 정하는 옵션입니다. 위에서 우리는 hello.c 라는 소스를 가지고 일반적으로 hello 라는 이름의 실행화일을 만들고 싶어할 것입니다.

 $ gcc -o hello hello.c
       ^^^^^^^^

또는 다음과 같이 순서를 바꿔도 무방합니다.

 $ gcc hello.c -o hello
               ^^^^^^^^

워낙 유닉스 쪽은 명령행 방식이 전통적으로 주된 방식이라 명령행에서 하는 일은 뛰어납니다.

당연히 실행을 하려면 ./hello 라고 하셔야 합니다. 결과는 다음처럼 나오겠지요?

 $ ./hello
 Hello, Linux Girls! =)
 $

주의

제일 안좋은 습관 중 하나가 바로 테스트용으로 만든 소스라고 다음처럼 하는 것입니다.

 $ gcc -o test test.c
 $ test
 $

문제를 알아내기 위하여 위에서 작성한 hello.c 를 컴파일/링크해봅시다.

 $ gcc -o test hello.c
 $ test
 $

원하는 문자열이 출력되지 않았습니다. -.-

 $ ./test
 Hello, Linux Girls! =)
 $

-c 옵션

어떤 이유로 오로지 컴파일(compile) 작업만 하고 싶은 경우가 있습니다. 그럴 때는 다음과 같이 합니다.

 $ gcc -c hello.c
 $

그 결과 만들어지는 화일은 전통적으로 hello.c 에서 .c 부분을 떼어내고 .o 를 붙인 화일입니다. 오브젝트 화일, 목적 화일이라고 하지요.

hello.o 라는 화일이 만들어집니다.

여러분은 C 언어로 조금이라도 복잡한 프로그램을 만들기 시작하면 여러 개의 소스로 나누어서 전체 프로그램을 짜게 됩니다. 그럴 때는 각 소스가 전체 기능에 기여하는 특정 기능의 함수들을 가지게 되고 오로지 한 녀석만 main 함수를 가집니다.

만약 어떤 프로그램이 foo.c, bar.c 이렇게 두 개의 소스로 이루어져 있다고 합시다. 이럴 때는 다음과 같이 하는 것이 가능합니다.

방법(1)

 $ gcc -o baz foo.c bar.c
 $ ./baz
방법(2)

 $ gcc -c foo.c
 $ gcc -c bar.c

          또는
 
 $ gcc -c foo.c bar.c
 $ gcc -o baz foo.o bar.o
              ^^^^^^^^^^^
 $ ./baz

위에서 보면 "아니! C 컴파일러에 .c 즉 소스 화일이 아닌 오브젝트 화일도 막 써주나?"라는 생각을 하시게 될 겁니다.

그렇습니다! 왜냐? gcc 는 정확히 말해서 C 컴파일러가 아닙니다. gcc 라는 실행 화일 자체는 "C 컴파일러를 돌리는 녀석"입니다.

더욱 더 정확히 말해보겠습니다.

C 언어는 기본적으로 두 가지 과정을 거쳐야만 실행화일을 만들어냅니다.

  1. 컴파일 ( .c -------> .o )
  2. 링크 ( .o -------> 실행화일 a.out )

1. 과정을 실제로 맡는 것은 cc1 이라는 녀석이고 2. 과정을 맡는 것은 ld 라는 링커(linker)입니다.

gcc 는 상당히 편리한 도구로서 .c, .o 등의 화일명 꼬리말을 보고 적절하게 C 컴파일러와 링커를 불러다가 원하는 실행화일을 만들어줍니다. gcc 는 "컴파일러와 링커를 불러주는 대리인"입니다.

hello.c 를 괜히 어렵게 컴파일/링크해봅시다 ^^

 $ gcc -c hello.c
          ^^^^^^^
 $ gcc -o hello hello.o
                ^^^^^^^

gcc 가 얼마나 똑똑피한 놈인지 알아보죠.

 $ gcc -c hello.o

이게 무슨 의미가 있겠습니까? ^^

 gcc: hello.o: linker input file unused since linking not done

위와 같이 불평합니다. 링크 과정을 수행하지 않으므로 링커에 대한 입력 화일인 hello.o 를 사용하지 않았다!

-I 옵션

#include 문장에서 지정한 헤더 화일이 들어있는 곳을 정하는 옵션입니다. 아주 많이 사용되는 옵션 중 하나입니다.


 #include <stdio.h>
 #include "my_header.h"

전자( <> 문자를 쓴 경우 )는 시스템 표준 헤더 디렉토리인 /usr/include를 기준으로 화일을 찾아서 포함시킵니다. 표준 디렉토리이지요.

후자( "" 문자를 쓴 경우 )는 지금 컴파일러가 실행되고 있는 현재 디렉토리를 기준으로 헤더 화일을 찾습니다.

이 두 디렉토리가 아닌 곳에 대해서는 명시적으로 -I<디렉토리> 로 정해줍니다.

 $ gcc -c myprog1.c -I..
 $ gcc -c myprog1.c -Iinclude

첫번째는 헤더 화일이 현재 소스 하위 디렉토리(..)에 있다는 뜻이며 두번째는 현재 디렉토리의 include라는 디렉토리에 들어있다는 뜻입니다.

-I 옵션은 얼마든지 여러번 쓸 수 있으며 주어진 순서대로 헤더 화일을 검색합니다.

주의

디렉토리명은 -I 라는 문자 바로 다음에 붙여서 씁니다. 즉 -I <디렉토리>라는 식이 아니라 -I<디렉토리> 입니다. 또한 유닉스에 있어 표준 헤더 화일 디렉토리는 /usr/include 라는 사실을 기억하시기 바랍니다. 또한 리눅스에 있어서는 커널 소스가 아주 중요한데 리눅스 고유의 기능을 쓰는 리눅스용 프로그램의 경우에는 /usr/include/linux, /usr/include/asm, /usr/include/scsi (최신 커널의 경우) 라는 디렉토리가 꼭 있어야 하며 각각은 커널 소스의 헤더 디렉토리에 대한 링크입니다. 따라서 커널 소스를 꼭 설치해두셔야 합니다.

 /usr/include/linux   -------------->  /usr/src/linux/include/linux
 /usr/include/asm     -------------->  /usr/src/linux/include/asm  
 /usr/include/scsi    -------------->  /usr/src/linux/include/scsi

( 위에서 /usr/src/linux/include/asm은 사실 대부분의 경우 /usr/src/linux/include/asm-i386 이라는 디렉토리에 대한 링크입니다 )

각각 linux는 일반적인 C 헤더 화일, asm은 각 아키텍쳐별 의존적인 어셈블리 헤더 화일, 맨 마지막은 SCSI 장치 프로그래밍에 쓰이는 헤더 화일이 들어있는 곳입니다.

일반적으로 커널 소스( 약 6 메가 이상되는 소스 )는 /usr/src 에서 tar, gzip으로 풀어두는 것이 관례입니다.

맨 처음 프로그래밍을 할 때는 그렇게 많이 쓰지는 않는 옵션이지만 여러분이 다른 소스를 가져다 컴파일할 때 아주 많이 보게 되는 옵션이므로 일단 이해는 할 수 있어야겠죠?

-l 옵션과 -L 옵션

옵션을 제대로 이해하기에 앞서 ``라이브러리''라는 것에 대한 이야기를 먼 저 하지 않으면 안될 듯 하군요.

  • 라이브러리


       ``라이브러리(Library)''라는 것은 개념상 영어 단어 그대로입니다.
      무엇인가 유용한 지식을 한 곳에 모아둔 곳이라는 개념이지요.

       C 프로그래밍을 하다 보면 반복적으로 사용하게 되는 함수들이 있기
      마련이고 그것은 하나의 함수로 떼내어 어디에서든 유용하게 사용할
      수 있도록 합니다.

       이 함수가 극도로 많이 사용되는 경우에는 ``라이브러리''라는 것으
      로 만들어두고 매번 컴파일해둘 필요없이 가져다 사용할 수 있도록
      하지요.

       라이브러리에 대한 얘기는 다음 번에 또 하게 되겠지만 일단 지금
      필요한 지식만 쌓기로 하겠습니다.

       일반적으로 관례상 라이브러리는 화일명 끝이 .a 로 끝납니다.
      여기서 a 는 Archive 라는 의미일 것입니다.

       라이브러리의 예를 들어보도록 하죠. 지금 /usr/lib 디렉토리를 한
      번 구경해보십시요. 정말로 많은 라이브러리들이 있지요.

      libc.a
      libm.a
      libdb.a
      libelf.a
      libfl.a
      libg++.a
      libg.a
      libncurses.a
      libreadline.a
      libvga.a
      등등...

       이러한 라이브러리는 우리가 컴파일 과정을 거쳐서 만든 .o 화일을
      한 곳에 통들어 관리하는 것에 불과합니다. 따라서 archive 를 의미
      하는 .a 라고 이름을 짓게 된 것이죠. 라이브러리는 ``도서관''으로
      서 그냥 .o 를 무작위로 집어넣은 것은 아니고 당연히 도서관에는
      소장하고 있는 책에 대한 목록(index)을 가지듯 포함되어 있는 .o
      에 대한 인덱스(index)를 가지고 있습니다.

       라이브러리는 다음과 같이 구성되어 있다고 할 수 있는 것입니다.

            라이브러리 = 목차(index) + ( a.o + b.o + c.o + ... )
        
       libc.a 를 가지고 한 번 놀아볼까요? 라이브러리 아카이브를 관리하
      는 ar 이라는 GNU 유틸리티를 써보겠습니다.

      $ cd /usr/lib
      $ ar t libc.a
      assert-perr.o
      assert.o
      setenv.o
      ftime.o
      psignal.o
      mkstemp.o
      sigint.o
      realpath.o
      cvt.o
      gcvt.o
      ctype-extn.o
      ctype.o
      <등등... 계속>

      $ ar t libc.a | grep printf
      iofprintf.o
      ioprintf.o
      iosprintf.o
      iovsprintf.o
      iovfprintf.o
      printf_fp.o
      vprintf.o
      snprintf.o
      vsnprintf.o
      asprintf.o
      vasprintf.o
      printf-prs.o
      reg-printf.o
      $

       위에서 볼 수 있다시피 .o 화일들이 그 안에 들어있습니다.

       <주목>
       유닉스에서 라이브러리 이름은 lib 로 시작합니다.

간단하게 라이브러리를 하나 만들어서 사용해보도록 합시다.

이번 예제는 3 개의 화일로 이루어졌습니다.

        myfunc.h
        myfunc.c
        hello.c

첫번째 myfunc.h 헤더 화일의 내용입니다.


extern void say_hello ( void );

두번째 myfunc.c, 실제 함수 정의부입니다.


#include <stdio.h>
#include "myfunc.h"

void 
say_hello ( void )
{
  printf ( "Hello, Linux guys!\n" );
}

마지막으로 메인 함수(main)가 들어있는 hello.c 입니다.


#include "myfunc.h"

int
main ( void )
{
  say_hello ();
  return 0;
}

main 함수에서 say_hello 라는 함수를 사용하게 됩니다. 이 정도야 그냥 이렇게 해버리고 말죠 ^^

 $ gcc -o say_linux hello.c myfunc.c

하지만 라이브러리를 만들어보고 시험해보려고 하는 것이므로 일부러 어렵게 한 번 해보기로 하겠습니다.

 $ gcc -c myfunc.c
 $ ar r libmylib.a myfunc.o
 $ ar s libmylib.a
 $ ar t libmylib.a
 myfunc.o
 $ gcc -o say_linux hello.c -lmylib
                            ^^^^^^^^
 ld: cannot open -lmylib: No such file or directory

흠... 처음부터 만만치 않죠? ^^ 실패하긴 했지만 몇 가지를 일단 알아봅시다.

-l 옵션

링크(link)할 라이브러리를 명시해주는 옵션이 바로 -l ( 소문자 L ) 옵션입니다.

-I 옵션과 마찬가지로 바짝 붙여서 씁니다. 절대 떼면 안됩니다.

우리는 libmylib.a 라는 라이브러리를 만들어두었습니다. 그것을 사용하기 위해서는 -lmylib 라고 적어줍니다. 라이브러리 화일명에서 어떤 글자들을 떼내고 쓰는지 주목하십시요.

 libmylib.a
    ^^^^^  

앞의 lib 를 떼내고 맨 뒤에 붙는 .a 를 떼냅니다.

링크(link)라는 것이 어떤 것이 모르신다면 당장 C 프로그래밍 책을 다시 읽어보시기 바랍니다. 이 글에서 설명할 범위는 아닌 듯 합니다.

-L 옵션

ld 는 유닉스에서 사용되는 링커(Linker)입니다. C 프로그램 컴파일의 맨 마지막 단계를 맡게 되지요.

위에서 우리는 다음과 같은 에러 메세지를 만났습니다.

 ld: cannot open -lmylib: No such file or directory

자, 이제 배워야 할 옵션은 ``라이브러리의 위치를 정해주는'' -L ( 대문자 L ) 옵션입니다. 사용형식은 -L<디렉토리명> 입니다.

리눅스에서 어떤 라이브러리를 찾을 때는 /lib, /usr/lib, /usr/local/lib와 같은 정해진 장소에서만 찾게 되어 있습니다. 그것은 규칙이지요.

중요한 사실은 아무리 여러분 라이브러리를 현재 작업 디렉토리에 놓아두어도 ld 는 그것을 찾지 않는다는 사실입니다. ld 더러 라이브러리가 있는 장소를 알려주려면 다음과 같이 -L 옵션을 붙이십시요.

 $ gcc -o say_linux hello.c -lmylib -L.
                                    ^^^

-L. 은 현재 디렉토리에서 라이브러리를 찾으라는 말입니다. -L 옵션은 여러번 줄 수 있습니다.

성공적으로 컴파일되었을 겁니다.

 $ ./say_linux
 Hello, Linux guys!

지금까지 여러분은 gcc 옵션 중 두번째로 중요한 -I, -l, -L 옵션에 대하여 배우셨습니다. 그리고 또한 라이브러리 만들기에 대하여 맛보기를 하였습니다.


3. make 강좌

3.1 머릿말

소스 한두 개로 이루어진 C/C++ 언어 교양과목 과제물을 제출하는 것이 아니라면 약간만 프로젝트가 커져도 소스는 감당할 수 없을 정도로 불어나게 되고 그것을 일일이 gcc 명령행 방식으로 처리한다는 것은 상당히 곤역스러운 일입니다.

그래서 하나의 프로젝트를 효율적으로 관리하고 일관성있게 관리하기 위하여 Makefile 이라는 형식을 사용하고 make 라는 유틸리티를 사용합니다.

여러분이 리눅스에서 소스 형태로 되어 있는 것을 가져와서 컴파일하게 되면 보통 마지막에는 make 라는 명령, 또는 make <어쩌구> 이런 식으로 치게 됩니다.

make 라는 유틸리티는 보통 현재 디렉토리에 Makefile 또는 makefile 이라는 일정한 규칙을 준수하여 만든 화일의 내용을 읽어서 목표 화일(target)을 만들어냅니다. Makefile의 이름을 다르게 명시하고 싶을 때는 다음과 같이 합니다.

        $ make -f Makefile.linux

보통 멀티플랫폼용 소스들은 Makefile.solaris, Makefile.freebsd, Makefile.hp 이런 식으로 Makefile 을 여러 개 만들어두는 경향이 있지요. 또는 적절하게 만들어두어 다음과 같이 make <플랫폼> 라는 식으로 하면 컴파일되도록 하기도 합니다.

        $ make linux

이런 일은 보통의 관례일 뿐이죠. 더 예를 들어보자면 이런 식입니다. 우리가 커널 컴파일 작업할 때를 보십시요.

        $ make config           /* 설정 작업을 한다 */
        $ make dep              /* 화일 의존성을 검사한다 */
        $ make clean            /* 만든 화일들을 지우고 
                                   깨긋한 상태로 만든다 */
        $ make zImage           /* zImage(압축커널)를 만든다 */
        $ make zlilo            /* 커널을 만들고 LILO를 설정한다 */
        $ make bzImage          /* bzImage(비대압축커널)를 만든다 */
        $ make modules          /* 커널 모듈을 만든다 */
        $ make modules_install  /* 커널 모듈을 인스톨한다 */

복잡한 것같아도 우리는 항상 make, make, make ... 일관성있게 make 라고만 쳐주면 됩니다. ^^ 분량이 작은 소스들의 경우에는 일반적으로 다음만 해도 되는 경우가 많죠.

        $ make  또는 make all
        $ make install

영어권에 사는 사람들에게는 더욱 친밀하게 느껴질 겁니다. 그렇겠죠? ``만들라!''라는 동사를 사용하고 있는 것이고 그 다음에는 그들의 정상적인 어순에 따라 목적어가 나오죠.

        $ make install.man

또한 관례상 ``맨페이지'' 같은 것은 별도로 인스톨하도록 배려하는 경우가 많습니다. 프로그램에 대해 잘 아는 사람이라면 맨페이지를 자질구레하게 설치하고 싶지 않을 때도 많으니까요.

다른 사람에게 공개하는 소스라면 더욱 make 를 사용해야 합니다. 그들뿐 아니라 여러분 자신도 make 라고만 치면 원하는 결과가 나올 수 있도록 하는 것이 좋습니다. 많은 소스를 작성하다 보면 여러분 스스로도 까먹기 쉽상입니다.

일단 make를 사용하는 일반적인 관례를 익히는 것이 중요하다고 봅니다. 리눅스 배포판 패키지만 설치하지 마시고 적극적으로 소스를 가져다 컴파일해보십시요. 실력이든 꽁수든 늘기 시작하면 여러분은 더욱 행복해지실 수 있습니다. =)

3.2 make 시작해 봅시다.

일관성있게 make라고만 치면 모든 일이 술술 풀려나가도록 하는 마술은 Makefile이라는 것을 어떻게 여러분이 잘 만들어두는가에 따라 결정됩니다. 바로 이 Makefile 을 어떻게 만드는지에 대하여 오늘 알아봅니다.

상황 1)

        $ gcc -o foo foo.c bar.c

여기서 foo 라는 실행화일은 foo.c, bar.c 라는 2 개의 소스로부터 만들어지고 있습니다.

여러분이 지금 계속 코딩을 하고 있는 중이라면 이 정도쯤이야 가상콘솔 또는 X 터미널을 여러 개 열어두고 편집하면서 쉘의 히스토리 기능을 사용하면 그만이지만 하루 이틀 계속 해간다고 하면 곤역스러운 일이 아닐 수 없습니다.

자, 실전으로 들어가버리겠습니다. vi Makefile 해서 만들어봅시다. ( 편집기는 여러분 마음 )


 foo:   foo.o bar.o 
        gcc -o foo foo.o bar.o

 foo.o: foo.c
        gcc -c foo.c

 bar.o: bar.c
        gcc -c bar.c

입력하는데 주의하실 것이 있습니다. 자, 위 화일을 보십시요. 형식은 다음과 같습니다.


 목표:  목표를 만드는데 필요한 구성요소들...
        목표를 달성하기 위한 명령 1
        목표를 달성하기 위한 명령 2
        ...

Makefile은 조금만 실수해도 일을 망치게 됩니다.

맨 첫번째 목표인 foo 를 살펴보죠. 맨 첫 칸에 foo: 라고 입력하고 나서 foo가 만들어지기 위해서 필요한 구성요소를 적어줍니다. foo가 만들어지기 위해서는 컴파일된 foo.o, bar.o 가 필요합니다. 각 요소를 구분하는데 있어 콤마(,) 같은 건 사용하지 않고 공백으로 합니다.

중요! 중요! 그 다음 줄로 넘어가서는 <탭>키를 누릅니다. 꼭 한 번 이상은 눌러야 합니다. 절대 스페이스키나 다른 키는 사용해선 안됩니다. 목표 화일을 만들어내기 위한 명령에 해당하는 줄들은 모두 <탭>키로 시작해야 합니다. Makefile 만들기에서 제일 중요한 내용입니다. <탭>키를 사용해야 한다는 사실, 바로 이것이 중요한 사실입니다.

foo를 만들기 위한 명령은 바로 gcc -o foo foo.o bar.o 입니다.

다시 한 번 해석하면 이렇습니다. foo 를 만들기 위해서는 foo.o와 bar.o가 우선 필요하다.( foo: foo.o bar.o )

일단 foo.o, bar.o 가 만들어져 있다면 우리는 gcc -o foo foo.o bar.o 를 실행하여 foo 를 만든다.

자, 이제부터 사슬처럼 엮어나가는 일만 남았습니다.

foo를 만들려고 하니 foo.o와 bar.o 가 필요합니다!

그렇다면 foo.o는 어떻게 만들죠?


 
 foo.o: foo.c
        gcc -c foo.c

바로 이 부분입니다. foo.o는 foo.c를 필요로 하며 만드는 방법은 gcc -c foo.c입니다.

그 다음 bar.o 는 어떻게 만들죠?


 bar.o: bar.c
        gcc -c bar.c

이것을 만들려면 이것이 필요하고 그것을 만들기 위해서는 또 이것이 필요하고...

소스를 만들어서 해봅시다.

  • foo.c 의 내용

extern void bar ( void );

int
main ( void )
{
  bar ();
  return 0;
}

  • bar.c 의 내용

#include <stdio.h>

void
bar ( void )
{
  printf ( "Good bye, my love.\n" );
}

Makefile을 위처럼 만들어두고 그냥 해보죠.

        $ make 또는 make foo
        gcc -c foo.c
        gcc -c bar.c
        gcc -o foo foo.o bar.o

명령이 실행되는 순서를 잘 보십시요. 여기서 감이 와야 합니다. ^^

        $ ./foo
        Good bye, my love.

다시 한 번 실행해볼까요?

        $ make
        make: `foo' is up to date.

똑똑한 make는 foo를 다시 만들 필요가 없다고 생각하고 더 이상 처리하지 않습니다.

이번에는 foo.c 를 약간만 고쳐봅시다. return 0; 라는 문장을 exit (0); 라는문장으로 바꾸어보죠. 그리고 다시 한 번 다음과 같이 합니다.

        $ make
        gcc -c foo.c
        gcc -o foo foo.o bar.o

자, 우리가 원하던 결과입니다. 당연히 foo.c 만 변화되었으므로 foo.o 를 만들고 foo.o가 갱신되었으므로 foo도 다시 만듭니다. 하지만 bar.c는 아무변화를 겪지 않았으므로 이미 만들어둔 bar.o 는 그대로 둡니다.

소스크기가 늘면 늘수록 이처럼 똑똑한 처리가 필요하지요.

        $ rm -f foo
        $ make
        gcc -o foo foo.o bar.o

이것도 우리가 원하던 결과입니다. foo 실행화일만 살짝 지웠더니 make는 알아서 이미 있는 foo.o, bar.o 를 가지고 foo 를 만들어냅니다. :)

상황 2) 재미를 들였다면 이번에는 청소작업을 해보기로 합시다.


 clean:
        rm -f foo foo.o bar.o

이 두 줄을 위에서 만든 Makefile 뒷부분에 추가해보도록 합시다.

        $ make clean
        rm -f foo foo.o bar.o
        $ make
        gcc -c foo.c
        gcc -c bar.c
        gcc -o foo foo.o bar.o

make clean이라는 작업 또한 중요한 작업입니다. 확실히 청소를 보장해주어야 하거든요.

make, make clean 이런 것이 되면 상당히 멋진 Makefile 이라고 볼 수 있죠? 이번 clean 에서 보여드리고자 하는 부분은 이런 것입니다.

우리의 머리 속에 clean 이라는 목표는 단지 화일들을 지우는 일입니다.

clean: 옆에 아무런 연관 화일들이 없지요?

그리고 오로지 rm -f foo foo.o bar.o 라는 명령만 있을 뿐입니다. clean이라는 목표를 수행하기 위해 필요한 것은 없습니다. 그러므로 적지 않았으며 타당한 make 문법입니다.

상황 3)


 all: foo

이 한 줄을 Makefile 맨 앞에 넣어두도록 합시다.

        $ make clean
        $ make all
        gcc -c foo.c
        gcc -c bar.c
        gcc -o foo foo.o bar.o

이번예는 all 이라는 목표에 그 밑에 나오는 다른 목표만이 들어있을 뿐, 아무런 명령도 없는 경우입니다. 보통 우리는 make all 하면 관련된 모든 것들이 만들어지길 원합니다.

 all: foo1 foo2 foo3
 foo1: <생략>
 foo2: <생략>
 foo3: <생략>

이런 식으로 해두면 어떤 장점이 있는지 알아봅시다.

보통 make all 하면 foo1, foo2, foo3가 모두 만들어집니다. 그런데 어떤 경우에는 foo1만 또는 foo2만을 만들고 싶을 때도 있을 겁니다. 괜히 필요없는 foo3 같은 것을 컴파일하느라 시간을 보내기 싫으므로 우리는 단지 다음과 같이만 할 겁니다.

        $ make foo1
        $ make foo2

물론 일반적으로 다 만들고 싶을 때는 make all 이라고만 하면 됩니다.

make all 이건 아주 일반적인 관례이지요. 그리고 외우기도 쉽잖아요?

3.3 꼬리말 규칙, 패턴 규칙

잘 관찰해보시면 어쩌구.c -----------> 어쩌구.o 라는 관계가 매번 등장함을 알 수 있습니다. 이것을 매번 반복한다는 것은 소스 화일이 한 두 개 정도일 때야 모르지만 수십 개가 넘게 되면 정말 곤역스러운 일이라고 하지 않을 수 없지요.

다음과 같은 표현을 Makefile 에서 보는 경우가 많을 겁니다.


 .c.o:
        gcc -c ${CFLAGS} $<

여기서 .c.o 의 의미를 생각해보겠습니다. ".c 를 입력화일로 받고 .o 화일을 만든다"

        gcc -c ${CFLAGS} $<

이 문자을 보면 일단 눈에 띄는 것은 ${CFLAGS}라는 표현과 $< 라는 암호와도 같은 표현입니다. 여기서는 일단 $< 라는 기호의 의미를 알아보겠습니다.

유닉스에서 쉘을 잘 구사하시는 분들은 눈치채셨을 겁니다. 작다 표시(<)는 리다이렉션에서 입력을 의미하는 것을 아십니까? 그렇다면 $< 는 바로 .c.o 라는 표현에서 .c 즉 C 소스 화일을 의미합니다.

예를 들어 foo.c 가 있다면 자동으로

        gcc -c ${CFLAGS} foo.c

가 수행되며 gcc 에 -c 옵션이 붙었으므로 foo.o 화일이 만들어질 것입니다.

3.4 GNU make 확장 기능

.c.o 라는 전통적인 표현 말고 GNU 버전( 우리가 리눅스에서 사용하는 것은 바로 이것입니다 )의 make 에서 사용하는 방법을 알아봅시다.

위에서 예로 든 것을 GNU 버전의 make 에서 지원하는 확장문법을 사용하면 다음과 같습니다.


 %.o: %.c
        gcc -c -o $@ ${CFLAGS} $<

그냥 설명 전에 잘 살펴보시기 바랍니다.

우리가 위에서 알아보았던 표준적인 .c.o 라는 꼬리말 규칙(Suffix rule)보다 훨씬 논리적이라는 것을 발견하셨습니까?

우리가 바로 전 강의에서 main.o : main.c 이런 식으로 표현한 것과 같은 맥락이지요? 이것을 우리는 패턴 규칙(Pattern rule)이라고 부릅니다. 콜론(:) 오른쪽이 입력 화일이고 왼쪽이 목표 화일입니다. 화일명 대신 퍼센트(%) 문자를 사용한 것만 유의하면 됩니다. 여기서 foo.c 라는 입력화일이 있다면 % 기호는 foo 만을 나타냅니다.

        gcc -c -o $@ ${CFLAGS} $<

라는 표현을 해석해봅시다. ( 후  마치 고대 문자판을 해석하는 기분이 안드십니까? ^^ )

$< 는 입력화일을 의미하고 $@ 은 출력화일을 의미합니다. .c.o와 같은 꼬리말 규칙과 별 다를 바 없다고 생각하실 지 모르나 -o $@ 를 통하여 .o 라는 이름 말고 전혀 다른 일도 해낼 수 있습니다.

다음 예는 그냥 이런 예가 있다는 것만 한 번 보아두시기 바랍니다.


 %_dbg.o: %.c
        gcc -c -g -o $@ ${CFLAG} $<

 DEBUG_OBJECTS = main_dbg.o edit_dbg.o

 edimh_dbg: $(DEBUG_OBJECTS)
        gcc -o $@ $(DEBUG_OBJECTS)

%_dbg.o 라는 표현을 잘 보십시요. foobar.c 라는 입력화일(%.c)이 있다면 % 기호는 foobar 를 가리키므로 %_dbg.o 는 결국 foobar_dbg.o 가 됩니다.

기호정리

 $<     입력 화일을 의미합니다. 콜론의 오른쪽에 오는 패턴을 치환합니다.
 $@     출력 화일을 의미합니다. 콜론의 왼쪽에 오는 패턴을 치환합니다.
 $*     입력 화일에서 꼬리말(.c, .s 등)을 떼넨 화일명을 나타냅니다.

역시 GNU 버전이라는 생각이 들지 않으시는지요?

3.5 매크로(Macro) 기능

앞에서도 잠깐씩 나온 ${CFLAGS} 라는 표현을 보도록 합시다.

gcc 옵션도 많이 알고 make을 능수능란하게 다룰 수 있는 사람들은 다음과 같이 해서 자신의 프로그램에 딱 맞는 gcc 옵션이 무엇인지 알아내려고 할 것입니다.

 $ make CFLAGS="-O4"
 $ make CFLAGS="-g"

이제 매크로에 대한 이야기를 나눠볼까 합니다. 이 이야기를 조금 해야만 위의 예를 이해할 수 있다고 보기 때문입니다. 그냥 시험삼아 해보십시다. 새로운 것을 배우기 위해서는 꼭 어떤 댓가가 와야만 한다는 생각을 버려야겠지요?


 myprog: main.o foo.o
        gcc -o $@ main.o foo.o

이것을 괜히 어렵게 매크로를 이용하여 표현해보기로 하겠습니다.


 OBJECTS = main.o foo.o
 myprog: $(OBJECTS)
        gcc -o $@ $(OBJECTS)

여러분은 보통 긴 Makefile을 훔쳐 볼 때 이런 매크로가 엄청나게 많다는 것을 보신 적이 있을 겁니다. ^^


 ROOT = /usr/local
 HEADERS = $(ROOT)/include
 SOURCES = $(ROOT)/src

예상하시듯 위에서 HEADERS는 당연히 /usr/local/include가 되겠지요?

다음과 같은 문장도 있습니다.


 ifdef XPM
     LINK_DEF = -DXPM
 endif

  $ make XPM=yes

이렇게 하면 ifdef   endif 부분이 처리됩니다.

자, make CFLAGS="-O" 이런 명령을 한 번 봅시다. ${CFLAGS}에서 {} 표현은 유닉스 쉘에서 변수값을 알아낼 때 쓰는 표현입니다. CFLAGS 값을 여러분이 Makefile에 고정적으로 집어넣지 않고 그냥 make 만 실행하는 사람에게 선택권을 주기 위해서 사용하거나 자기 스스로 어떤 옵션이 제일 잘 맞는지 알아보기 위해서 사용합니다. 다른 옵션으로 컴파일하는 것마다 일일이 다른 Makefile을 만들지 말고 가변적인 부분을 변수화하는 것이 좋습니다.

3.6 마지막 주의 사항


 target:
        cd obj
        HOST_DIR=/home/e 
        mv *.o $HOST_DIR

하나의 목표에 대하여 여러 명령을 쓰면 예기치 않은 일이 벌어집니다. 기술적으로 말하자면 각 명령은 각자의 서브쉘에서 실행되므로 전혀 연관이 없습니다. -.- cd obj 도 하나의 쉘에서 HOST_DIR=/home/e도 하나의 쉘에서 나머지도 마찬가지입니다. 각기 다른 쉘에서 작업한 것처럼 되므로 cd obj 했다 하더라도 다음번 명령의 위치는 obj 디렉토리가 아니라 그대로 변함이 없이 현재 디렉토리입니다. 세번째 명령에서 HOST_DIR 변수를 찾으려 하지만 두번째 명령이 종료한 후 HOST_DIR 변수는 사라집니다.


 target:
        cd obj ; \
        HOST_DIR=/hom/e ; \
        mv *.o $$HOST_DIR

이렇게 적어주셔야 합니다. 세미콜론으로 각 명령을 구분하지요. 처음 두 줄의 마지막에 쓰인 역슬래쉬(\) 문자는 한 줄에 쓸 것을 여러 줄로 나누어 쓴다는 것을 나타내고 있습니다.

주의! 세번째 줄에 $HOST_DIR이 아니라 $$HOST_DIR인 것을 명심하십시요. 예를 하나 들어보죠. ^^


 all:
         HELLO="안녕하세요?";\
         echo $HELLO

Makefile의 내용을 이렇게 간단하게 만듭니다.

 $ make
 HELLO="안녕하세요?";\
 echo ELLO
 ELLO
<verb>

 우리가 원하는 결과가 아니죠?

 $HELLO를 $$HELLO로 바꾸어보십시요.

<verb>
 $ make
 HELLO="안녕하세요?";\
 echo $HELLO
 안녕하세요?


 all:
         @HELLO="안녕하세요?"; echo $$HELLO

명령의 맨 처음에 @ 문자를 붙여봅시다.

 $ make
 안녕하세요?

3.7 잠시 마치면서

Makefile에 대한 내용은 이것보다 훨씬 내용이 많습니다. 하지만 모든 것을 다 알고 시작할 수는 없겠지요? 이 정도면 어느 정도 충분하게 창피하지 않을 정도의 Makefile을 만들 수 있습니다.

참고로 autoconf/automake라고 하는 아주 훌륭한 GNU make 유틸리티를 시간나면 배워보시는 것도 좋습니다.

시간을 내서 리눅스에서의 C 프로그래밍에 필요한 다른 여러 가지 유틸리티들( 간접적이든 직접적이든 grep, awk, rcs, cvs 등 )의 간단/실전 사용법도 올려드릴까 생각 중입니다. ^^



출처: 이 문서는 나우누리 "Linux 사용자 모임" 12-2번 강좌란 게시판에 올라온 이만용님의 강좌 "gcc와 make에 대한 강좌"를 sgml문서로 만든 것입니다.


참고문헌

Running LINUX(Matt Welsh, Lar Kaufman), "오렐리 출판사"

크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기
Posted by 소리나는연탄.
TAGS ,

Leave your greetings here.

  
  
  
  
  
  
  
  
 


프로세스들은 시스템의 자원들의 할당을 위한 기본적인 단위이다. 각 프로세스는 자신만의 주소공간과 (보통) 한 개의 제어 쓰레드를 갖는다. 프로세스는 프로그램을 실행한다; 당신은 같은 프로그램을 실행하는데 여러개의 프로세스를 가질 수 있지만, 각각의 프로세스는 자신의 주소공간에서 자신의 프로그램 복제본을 갖고 다른 프로그램 복사본과 독립적으로 실행된다.

프로세스들은 계층적으로 구성된다. 각 프로세스는 그것을 만들었던 부모 프로세스를 갖는다. 주어진 부모 프로세스에 의해 만들어진 프로세스는 자식 프로세스라고 불린다. 자식 프로세스는 부모 프로세스로 부터 그 속성의 대부분을 상속받는다.

이 장은 프로그램에서 어떤 자식 프로세스를 만들고, 종료하고, 제어하는지에 대해서 설명하고 있다. 실제로, 새로운 자식 프로세스를 만들로, 새로운 프로세스가 프로그램을 실행시키고, 원래의 프로그램과 자식 프로세스가 조화롭게 수행되도록 하는데에는 세 가지의 독림적인 명령이 있다.

system 함수는 다른 프로그램을 실행시키기 위한 간단하고, 이식성 있는 메커니즘을 제공한다. 그들은 자동적으로 세 개의 단계를 거친다. 만일 당신이 이러한 일들을 하는데 세심하게 제어할 필요가 있다면, 당신은 각 단계를 개별적으로 수행하는 기본 함수들을 사용할 수 있다.


23. 1 명령 실행시키기

다른 프로그램을 실행하기 위한 가장 쉬운 방법은 system 함수를 사용하는 것이다. 이 함수는 실행되는 서브프로그램의 모든 작업을 수행하지만, 당신이 세밀하게 제어를 할 수는 없다: 당신이 어떤 것을 할 수 있기 전에 그 서브 프로그램이 종료될 때까지 기다려야만 한다.

함수 : int system (const char *command)

이 함수는 쉘 커맨드로써 command를 실행한다. GNU C 라이브러리에서, 그것은 항상 커맨드를 실행하도록 디폴트 쉘 sh를 사용한다. 특별하게, 실행하기 위한 프로그램을 찾기 위해서 PATH에 있는 디렉토리들을 탐색한다. 반환값은 만일 쉘 프로세스를 만들기가 불가능하면 -1을 반환하고 그렇지 않다면 쉘 프로세스의 상황이 된다. 이 상황 코드가 어떻게 해석되어지는지에 대해서는 23. 6절 [Process Completion] 참조. system 함수는 헤더파일 `stdlib. h'에 선언되어 있다.
이식성 노트 : 어떤 C에서는 다른 프로그램을 실행할 수 있는 커맨드 프로세스의 개념을 가지지 않을 수도 있다. 당신은 커맨드 프로세스가 존재하는지에 대한 여부를 system(NULL)을 실행해봄으로써 알아낼 수 있다; 만일 그 반환값이 0이 아니면, 커맨드 프로세서는 유용하다.

popen 과 pclose 함수들은(10. 2절 [Pipe to a Subprocess] 참조) system 함수와 밀접하게 연관되어 있다. 그들은 실행되고 있는 커맨드의 표준 입/출력 채널과 통신하도록 부모 프로세스에게 허용한다.


23. 2 프로세스 만들기에 대한 원칙

이 절은 프로세스와 프로세스를 만드는데 포함되는 단계와 그것이 다른 프로그램을 실행하도록 만드는데 대한 개요가 있다. 각 프로세스는 프로세스 ID 번호에 의해 이름지어진다. 어떤 단일한 프로세스 ID는 그것이 만들어질 때 각 프로세스에게 할당되어진다. 프로세스의 수명이 다하면 부모 프로세스에게 보고된다; 그때, 그 프로세스 ID에 포함된 프로세스의 모든 자원들은 해제된다.

프로세스들은 fork 시스템 호출로 만들어진다( 그래서 새로운 프로세스를 만드는 동작을 forking a process라고 부르기도 한다. ). fork에 의해 만들어진 자식 프로세스는 오직 자신의 프로세스 ID를 제외하고는, 원래 부모 프로세스의 완전한 복제이다.

자식 프로세스를 만든 후에, 부모와 자식 프로세스 모두는 정상적으로 실행을 계속한다. 만일 당신이 당신의 프로그램에서 실행을 계속하기 전에 자식 프로세스가 실행을 끝내도록 기다리기 원한다면, fork 연산을 수행한 후에 wait 나 waitpid(23. 6절 [Process Completion] 참조)를 호출함으로써 명백하게 이일을 하도록 해야만 한다. 그들 함수들은 자식프로세스가 왜 종료되었는지에 대한 제한된 정보_예를 들면, exit 상황코드_를 준다.

새롭게 만들어진 자식 프로세스는 fork가 return을 호출한 지점에서 부모 프로세스와 같은 프로그램을 실행한다. 당신은 그 프로그램이 부모 프로세스 또는 자식 프로세스에서 실행되고 있는지에 대해서 알기 위해서 fork로부터의 반환값을 사용할 수 있다.

여러 개의 프로세스가 같은 프로그램을 실행하기는 때때로 유용하다. 그러나 자식 프로세스는 exec 함수들중의 하나를 사용해서 다른 프로그램을 실행할 수 있다; 23. 5절 [Executing a File] 참조. 프로세스가 실행시키고 있는 프로그램을 프로세스 이미지(process image)라고 부른다. 새로운 프로그램의 실행을 시작하는 것은 프로세스가 그 전의 프로세스 이미지에 대한 모든 것을 잊게 한다; 새로운 프로그램이 종료될 때, 그 전의 프로세스 이미지를 반환하지 않고, 프로그램처럼 종료한다.


23. 3 프로세스 식별

pid_t 데이터 타입은 프로세스 ID들을 나타낸다. 당신은 getpid를 호출함으로써 프로세스의 ID를 얻을 수 있다. getppid 함수는 현재 프로세스의 (이것은 또한 부모 프로세스의 ID로써 알려져 있다. ) 부모 프로세스 ID를 반환한다. 당신의 프로그램에서 그들 함수를 사용하기 위해서는 `unistd. h'와 `sys/types. h'의 헤더파일을 포함해야한다.

데이터 타입 : pid__t

pid_t 데이터 타입은 프로세스 ID를 표현하는 부호화 정수 타입이다. GNU 라이브러리에서, 이것은 int 이다.

함수 : pid_t getpid (void)

getpid 함수는 현재 프로세스의 프로세스 ID를 반환한다.

함수 : pid_t getppid (void)

getppid 함수는 현재 프로세스의 부모 프로세스 ID를 반환한다.


23. 4 프로세스 만들기

fork 함수는 프로세스를 만드는 기본동작(primitive)이다. 그것은 헤더파일 `unistd. h'에 선언되어 있다.

함수 : pid_t fork (void)

fork 함수는 새로운 프로세스를 만든다. 만일 그 명령이 성공하면, 그곳에는 부모와 자식 프로세스가 존재하고 그 둘은 fork의 반환값을 서로 다른 값으로 보게된다. 자식 프로세스 안에서는 0의 값을 반환하고 부모 프로세스 안에서는 자식 프로세스의 ID를 반환한다. 만일 프로세스 만들기가 실패하면, fork는 부모 프로세스에게 -1의 값을 반환한다.
다음의 errno는 fork를 위해 정의된 에러 상황이다.

EAGAIN

다른 프로세스를 만들만한 충분한 시스템 자원이 없거나, 사용자가 이미 너무 많은 프로세스들을 실행시키고 있다.

ENOMEM : 프로세스는 시스템이 공급할 수 있는 것보다 더 많은 공간을 필요로 한다.

다음은 부모 프로세스와는 다른, 자식 프로세스의 정해진 속성이다.

자식 프로세스는 자신만의 단일한 프로세스 ID를 갖는다.
자식 프로세스의 부모 프로세스 ID는 그 자신의 부모 프로세스의 ID이다.
자식 프로세스는 부모 프로세스가 개방한 파일 기술자의 자신 소유의 복사본을 가진다. 부모 프로세스 안에서 연속적으로 속성이 변화하는 파일 기술자들은 자식 프로세스의 파일 기술자에게 영향을 미치지 않고, 그리고 자식 프로세스에 속한 파일기술자의 속성이 변한다고 해도 그것또한 부모 프로세스의 기술자에게 영향을 미치지 못한다. 8. 7절 [Control Operations] 참조.
자식 프로세스를 위하여 경과된 프로세서 시간은 0으로 설정된다; 17. 1절 [Processor Time] 참조.
자식 프로세스는 부모 프로세스에 의해 설정된 파일 락(lock)들을 상속받지 않는다. 8. 7절 [Control Operations] 참조.
자식 프로세스는 부모 프로세스에 의해 설정된 알람(alarm)을 상속받지 않는다. 17. 3절 [Setting an Alarm] 참조.
자식 프로세스를 위해서 미처리중인 시그널의 설정(21. 1. 3절 [Delivery of Signal] 참조)은 소거된다. (자식 프로세스는 부모 프로세스로 부터 블록된 시그널의 마스크와 시그널 동작을 상속받는다. )

함수 : pid_t vfork (void)

vfork 함수는 fork와 유사하지만 더 효율적이다; 하지만, 그것을 안전하게 사용하기 위해서 따랴야만 하는 제한이 있다.
fork는 호출된 프로세스의 주소공산의 완전한 복제본을 만들고 부모와 자식 프로세스가 독립적으로 실행되도록 허용하지만, vfork는 이 복제본을 만들지 않는다. 대신에, vfork로 만들어진 자식 프로세스는 exec 함수들중의 하나가 호출될 때까지 부모의 주소공간을 분배받는다. 그것은 부모 프로세스가 일시적으로 실행을 멈추게 됨을 의미한다. 당신은 vfork로 만들어진 자식 프로세스가 부모로부터 분배받은 전역 데이터나 심지어 지역 변수들의 갱신을 허용하지 않도록 매우 주의해야만 한다. 게다가, 자식 프로세스는 호출된 vfork 함수로부터 반환할 수 없다(또는 vfork 함수의 밖으로 long jump하는 것). 이것은 부모 프로세스의 제어 정보를 매우 혼란스럽게 만든다. 만일 의심이 된다면, 대신에 fork를 사용하라. 어떤 운영체제는 실제로 vfork를 지원하지 않는다.
GNU C 라이브러리는 모든 시스템에서 vfork를 사용하도록 허용하지만, vfork가 유용하지 않다면 실제로는 fork를 실행한다. 만일 당신이 vfork를 사용하기 위해서 적당하게 예방조치를 취한다면, 당신의 프로그램은 시스템에서 대신에 fork를 사용해서라도 여전히 작업할 것이다.


23. 5 파일 실행시키기

이 절은 프로세스 이미지로써 파일을 실행시키는 exec부류의 함수들을 설명한다. 당신은 자식 프로세스가 만들어진 후에 자식 프로세스가 새로운 프로그램을 실행하도록 그들 함수들을 사용할 수 있다. 이 부류의 함수들은 같은 일을 하지만, 인수를 정하는 방법은 차이가 있다. 그들은 헤더파일 `unistd. h'에 선언되어 있다.

함수 : int execv (const char *filename, char *const argv[])

execv 함수는 새로운 프로세스 이미지로써 파일이름 filename을 실행한다. 널-종료 문자열의 배열인 argv 인수는 실행시키려는 프로그램의 메인 함수에 argv 인수로써 제공하기 위한 값으로 사용된다. 이 배열의 마지막 요소는 반드시 널 포인터가 되어야만 한다. 관례대로, 이 배열의 첫 번째 요소는 디렉토리 이름이 없는 프로그램의 파일 이름이다. 어떻게 프로그램이 그들 인수들을 억세스 하는지에 대한 자세한 것은 22. 1절 [Program Arguments] 참조. 새로운 프로세스 이미지를 위한 환경은 현재 프로세스 이미지의 environ변수로부터 획득된다; 환경에 대한 자세한 정보는 22. 2절 [Environment Variables] 참조.

함수 : int execl (const char *filename, const char *arg(), . . . )

이것은 execv와 유사하지만, argv 문자열은 배열이 아니라 개별적으로 정해져있다. 널 포인터가 마지막 인수로 주어져야만 한다.

함수 : int execve (const chat *filename, chat *const argv[], chat *const env[])

이것은 execv와 유사하지만, env 인수를 통해 새로운 프로그램을 위한 환경을 명시적으로 지정하도록 허용한다. 이것은 environ변수와 같은 형식으로써, 문자열의 배열이다; 22. 2. 1 [Environment Access] 참조.

함수 : int execle (const char *filename, const char *arg(), char *const env[], . . . )

이것은 execl과 유사하지만, 명시적으로 새로운 프로그램을 위한 환경을 정하도록 허용한다. 환경 인수는 마지막 argv 인수로써 표시된 널 포인터의 뒤에 따르고, environ 변수와 같은 형식으로 문자열의 배열이 된다.

함수 : int execvp (const char *filename, char *const argv[])

execvp 함수는 filename에 슬래쉬가 포함되어 있지 않으면 filename으로부터 완전한 파일 이름을 찾기 위해서, PATH 환경변수에 있는 디렉토리를 탐색한다는 점을 제외하고는, execv 와 유사하다 (22. 2. 2 [Standard Environment] 참조. ). 이 함수는 사용자가 선택한 곳에서 그들을 찾기 때문에, 시스템유틸리티 프로그램들을 실행하기에는 유용하다. 쉘들은 사용자가 타이핑한 커맨드를 실행하기 위해서 그것을 사용한다.

함수 : int execlp (const char *filename, const char *arg(), . . . )

이 함수는 execvp 함수처럼 filename과 같은 이름을 탐색한다는 점을 제외하고는, execl 함수와 같다. 서로 받아들인 인수 리스트와 환경 리스트의 크기는 ARG_MAX 바이트 보다 크지 않아야만 한다. 27. 1절 [General Limits] 참조. GNU시스템에서 크기(ARG_MAX와 비교하여)는 각 문자열에서, 문자열 안에 있는 문자들의 개수에 char *의 크기를 더하고, char *의 크기의 배수로 반올림된 하나를 더한다. 다른 시스템들은 크기를 셈하기 위한 다른 규칙을 갖는다.

그들 함수들은 일반적으로 반환하지 않는다. 실패가 발생하면 -1의 값을 반환한다. 보통의 파일 이름 구문 에러(6. 2. 3절 [File Name Errors] 참조. )들에 더해서, 다음의 errno는 이 그들 함수들을 위해서 정의된 에러상황이다.

    E2BIG

    새로운 프로그램의 인수 리스트와 환경 리스트의 결합된 크기가 ARG_MAX 바이트보다 크다. GNU 시스템은 인수 리스트의 크기에 아무런 제한을 두지 않기 때문에 이 에러가 발생하지는 않지만, 그 인수가 유용한 메모리의 크기보다 크다면 ENOMEN의 에러는 발생할 것이다.

    ENOEXEC : 정해진 파일이 올바른 형식이 아니기 때문에 실행될 수 없다.

    ENOMEM : 정해진 파일을 실행시키는데는 현재 유용한 것보다 더 많은 공간이 필요하다.

만일 새로운 파일의 실행이 성공하면, 그것은 마치 그 파일을 읽은 것 그 파일의 억세스 타임 필드(access time field)를 갱신한다. 9. 8. 9절[File Times] 를 참조하여, 파일의 억세스 타임에 대한 자세한 정보를 얻어라. 그 파일이 폐쇄된 지점에서 다시 어떤 것도 정해지지 않았다면, 그것은 프로세스가 종료되기전에나 다른 프로세스 이미지가 실행되기 전인 어떤 지점이다.

새로운 프로세스 이미지를 실행하는 것은 새로운 위치로 인수와 환경 문자열을 복사하고, 메모리의 내용을 완전히 바꾸는 것이다. 그러나 프로세스의 많은 다른 속성들은 변경되지 않는다.

만일 프로세스 이미지 파일의 set-user-ID 와 set-group-ID 모드 비트가 설정되면, 이것은 프로세스의 유효 사용자 ID와 유효 그룹 ID에게 영향을 미친다. 그러한 개념들은 25. 2절 [Process Persona] 에 상세하게 설명되어 있다.

현존하는 프로세스 이미지에서 무시되도록 설정된 시그널들은 새로운 프로세스 이미지에서도 또한 무시되도록 설정된다. 모든 다른 시그널들은 새로운 프로세스 이미지에서 디폴트 동작으로 설정된다. 시그널에 대한 더 많은 정보는, 21장 [Signal Handling] 참조하라.

실행중인 프로세스 이미지에서 개방한 파일 기술자들은, 그들이 FD_CLOEXEC (close-on-exec) 플래그를 설정하지 않는다면, 새로운 프로세스 이미지에서도 개방된 채로 남겨진다. 개방된 상태로 남겨진 파일들은 실행중인 프로세스 이미지로부터 파일 락들을 포함한, 개방 파일 기술의 모든 속성을 상속받는다. 파일 기술자는 8장 [Low-Level I/O] 에 상세하게 설명되어 있다.

파일과 비교하여 스트림은, exec 함수들을 통해서도 살아남지 못한다, 왜냐하면 그들은 프로세스 자체의 메모리 안에 위치하고 있기 때문이다. 새로운 프로세스 이미지는 그들이 새롭게 만든 스트림을 제외하고는 아무런 스트림도 갖지 않는다. pre-exec프로세스 이미지 안에 있는 스트림들의 각각은 그 내부에 기술자를 갖고있고, 그들 기술자는 exec을 통해서 살아남는다. (그들은 FD_CLOEXEC의 설정을 가지지 않고 제공된 것. 새로운 프로세스 이미지는 fdopen을 사용해서 새로운 스트림에 그들을 재연결 할 수 있다. (8. 4절 [Descriptors and Streams] 참조. )


23. 6 프로세스 종료

이 절에서 설명하고 있는 함수들은 자식 프로세스가 종료하거나 멈추도록 기다리는데 사용되고, 그러한 상황인지의 여부를 알아보는데 사용된다. 그들 함수들은 헤더파일 `sys/wait. h'에 선언되어 있다.

함수 : pid_t waitpid (pid_t pid, int *status_ptr, int options)

waitpid 함수는 프로세스 ID를 pid로 가진 자식 프로세스로 부터 상황 정보를 요청하는데 사용된다. 일반적으로, 호출된 프로세스는 자식 프로세스가 종료됨으로써 유용한 상황정보를 만들 때까지 잠시 중지되어 있다.

pid 인수를 위한 다른 값들은 특별한 뜻을 갖는다. -1의 값이나 WAIT_ANY는 어떤 자식 프로세스를 위한 상황정보를 요청한다; 0의 값이나 WAIT_MYPGRP는 호출된 프로세스와 같은 프로세스 그룹에 있는 어떤 자식 프로세스를 위한 정보를 요청한다; 그리고 다른 음수값 - pgid는 프로세스 그룹 ID로써 pgid를 가진 자식 프로세스를 위한 정보를 요청한다.

만일 자식 프로세스를 위한 상황정보가 즉시 유용한 상태라면, 이 함수는 기다림이 없이 즉시 반환한다. 만일 한 개의 적합한 자식 프로세스보다 많은 프로세스가 유용한 상황 정보를 갖고있다면, 그둘중의 하나가 임의로 선택되고, 그 상황은 즉시 반환된다. 다른 적합한 자식 프로세스로 부터 상황을 얻기 위하여, 당신은 waitpid를 다시 호출할 필요가 있다.

options 인수는 비트마스크이다. 그 값은 0이나 WNOHANG와 WUNTRACED 플래그들이 비트별 OR로 조합되어질 것이다. 당신은 부모 프로세스가 기다리지 않을 것임을 지적하기 위해서 WNOHANG 플래그를 사용할 수 있다; 그리고 WUNTRACED플래그는 종료된 프로세스 뿐만 아니라 멈추어진 프로세스들로 부터 상황 정보를 요청하기 위해서 사용되어진다.

자식 프로세스로 부터의 상황정보는 status_ptr이 널 포인터가 아니라면, status_ptr이 가리키고 있는 오브젝트 안에 저장된다. 반환값은 보통 보고된 상황을 가진 자식 프로세스의 프로세스 ID 가 된다.

만일 WNOHANG 옵션이 지정됐고 어떤 자식 프로세스도 기다리고 있지 않다면, 그 값은 0이된다. 에러가 발생한 경우에 -1을 반환한다. 다음의 errno는 이 함수를 위해서 정의된 에러상황이다.

    EINTR

    그 함수는 호출된 프로세스에게 배달된 시그널에 의해 인터럽트 되어졌다. 21. 5절 [Interrupted Primitives] 참조.

    ECHILD

    그곳에 기다리고 있는 자식 프로세스가 아무 것도 없거나, 정해진 pid가 호출된 프로세스의 자식프로세스가 아니다.

    EINVAL : 올바르지 못한 값이 options 인수로써 공급되었다.

다음 기호 상수들은 waitpid 함수에게 pid 인수를 위한 값으로써 정의되었다.

    WAIT_ANY

    이 상수 매크로는 waitpid가 어느 자식 프로세스에 대한 상황정보를 반환하도록 지정되었다. (값은 -1)

    WAIT_MYPGRP

    이 상수는(값은 0) 호출된 프로세스와 같은 프로세스 그룹 안에 있는 어떤 자식 프로세스에 대한 상황정보를 반환하도록 지정되었다.

다음의 기호 상수들은 waitpid 함수의 options 인수를 위한 플래그로써 정의되었다. 당신은 그들은 비트별-OR 연산을 통해서 조합할 수 있다.

    WNOHANG

    이 플래그는 만일그곳에 준비된 자식 프로세스가 없다면, waitpid가 기다리지 않고 즉시 반환하도록 지정되었다.

    WUNTRACED

    이 플래그는 종료된 것뿐만 아니라 멈추어진 자식 프로세스에 대한 상황을 보고하도록 지정되었다.

함수 : pid_t wait (int *status_ptr)

이 함수는 waitpid의 간소화된 변형이고, 어떤 한 개의 자식 프로세스가 종료될 때까지 기다리는데 사용된다. 호출은:
wait (&status)
는 정확히 다음과 동등하다:
waitpid (-1, &status, 0)

다음은 기다림이 없이, 종료된 모든 자식 프로세스에서 보내온 상황을 얻기 위해서는 waitpid를 어떻게 사용하는지에 대한 예제가 있다. 이 함수는 SIGCHLD 시그널을 위한 핸들러로써 만들어졌는데, 그 시그널은 적어도 한 개의 자식 프로세스가 종료되었음을 알리기위해서 발생 된다.

void
sigchld_handler (int signum)
{
int pid;
int status;
while (1)
{
pid = waitpid (WAIT_ANY, &status, WNOHANG);
if (pid < 0)
{
perror ("waitpid");
break;
}
if (pid == 0)
break;
notice_termination (pid, status);
}
}


23. 7 프로세스 종료 상황들

만일 자식 프로세스의 종료 상황 값(22. 3절 [Program Termination] 참조. )이 0이면, waitpid 또는 wait에 의해 보고된 상황 값 또한 0이다. 당신은 다음의 매크로를 사용해서 반환된 상황값안에 있는 암호화된 정보를 테스트할 수 있다.

매크로 : int WIFEXITED (int status)

이 매크로는 만일 자식 프로세스가 exit 또는 _exit로써 정상적으로 종료되면 0이 아닌 값을 지닌다.

매크로 : int WEXITSTATUS (int status)

만일 status가 참인 상태의 WIFEXITED이면, 이 매크로는 자식프로세스로부터의 종료 상황 값의 하위 8비트를 반환한다. 22. 3. 2절 [Exit Status] 참조.

매크로 : int WIFSIGNALED (int status)

이 매크로는 만일 자식 프로세스가 처리되지 않은 시그널을 받았기 때문에 종료되었다면 0이 아닌 값을 반환한다. 21장 [Signal Handling] 참조.

매크로 : int WTERMSIG (int status)

만일 status가 참인 WIFSEGNALED이면, 이 매크로는 자식 프로세스를 종료시켰던 시그널의 시그널 번호를 반환한다.

매크로 : int WCOREDUMP (int status)

이 매크로는 자식 프로세스가 종료되고 코어를 생성했다면, 0이 아닌 값을 반환한다.

매크로 : int WIFSTOPPED (int status)

이 매크로는 만일 자식 프로세스가 멈추어져있다면 0이 아닌 값을 반환한다.

매크로 : int WSTOPSIG (int status)

만일 status가 참인 WIFSTOPPED 이면, 이 매크로는 자식 프로세스를 멈추게 한 원인이 된 시그널의 시그널 번호를 반환한다.


23. 8 BSD 프로세스 Wait 함수들

GNU 라이브러리는 BSD 유닉스와의 호환성을 위해서 그들과 연관된 기능들을 제공한다. BSD는 int 와 다르게 상황 값을 표현하는 union wait 데이터 타입을 사용한다. 두 개의 표현은 실제로 상호간에 변경 가능하다; 그들은 동일한 비트 패턴을 표현하기 때문이다. WEXITSTATUS 와 같은 매크로를 정의하고 있는 GNU C 라이브러리는 오브젝트의 둘중 한 종류를 선택해서 작업할 것이고, wait 함수는 status_ptr 인수로써 포인터의 한 종류를 받아들이도록 정의된다. 그들 함수들은 `sys/wait. h'에 선언되어 있다.

데이터 타입 : union wait

이 데이터 타입은 프로그램 종료 상황 값들을 표현하고, 다음과 같은 멤버들을 갖는다.

int w_termsig

이 멤버의 값은 매크로 WTERMSIG의 결과와 같다.

int w_coredump

이 맴버의 값은 매크로 WCOREDUMP의 결과와 같다.

int w_retcode

이 맴버의 값은 매크로 WEXITSTATUS의 결과와 같다.

int w_stopsig

이 맴버의 값은 매크로 WSTOPSIG의 결과와 같다.

직접적으로 이 맴버들을 억세스 하는 대신에, 당신은 동등한 매크로 를 사용하도록 하라.

함수: pid_t wait3 (union wait *status_ptr, int options, struct rusage *usage)

usage가 널 포인터라면, wait3는 waitpid(-1, status`ptr, options)와 같다. 만일 usage가 널 포인터가 아니라면, wait3는 *rusage에 있는 자식 프로세스를 위한 사용 형태를 저장한다(그러나 오직 멈추어 있는 것이 아니라, 종료된 자식 프로세스라면. )17. 5절 [Resource Usage] 참조.

함수 : pid_t wait4 (pid_t pid, union wait *status_ptr, int options, struct rusage *usage)

만일 usage가 널 포인터라면, wait4는 waitpid (pid, status`ptr, options)와 같다. 만일 usage가 널 포인터가 아니라면, wait4는 *rusage에 있는 자식 프로세스를 위하여 사용 형태들을 저장한다. (그러나 오직 자식 프로세스는 멈추어 있는 것이 아니라, 종료된 것이다. ) 17. 5절 [Resource Usage] 참조.


23. 9 프로세스 만들기 예제

다음은 내장 시스템과 유사한 함수를 어떻게 만들 것인지를 보여주는 예제 프로그램이다. 그것은 `sh -c command'와 동등한 것을 사용하여 command 인수를 실행한다.

#include <stddef. h>
#include <stdlib. h>
#include <unistd. h>
#include <sys/types. h>
#include <sys/wait. h>
/* 쉘 프로그램을 사용해서 command 를 실행한다. */
#define SHELL "/bin/sh"
int
my_system (const char *command)
{
int status;
pid_t pid;
if ((pid = fork()) == 0) { /* 이것은 자식 프로세스로써 쉘 커맨드를 실행한다. */
execl (SHELL, SHELL, "-c", command, NULL);
_exit (EXIT_FAILURE);
} else if (pid < 0) { /* fork가 실패하였으므로 실패를 보고하라. */
status = -1;
} else { /* 이것은 부모 프로세스로써, 자식이 수행을 완료할 때까지 기다린다. */
if (waitpid (pid, &status, 0) != pid)
status = -1;
}
return status;
}

당신이 이 예제에서 주목해야 할 것이 두 가지 있다. 프로그램에 공급된 첫 번째 argv 인수는 실행시키려는 프로그램의 이름을 표현한다는 것을 기억하라. 그것은 execl의 호출에서, SHELL에게 일단 실행하려는 프로그램의 이름이 주어지고 두 번째로 argv[0]을 위한 값을 공급하는 이유이다.

자식 프로세스에서 execl 호출은 만일 그것이 성공하면 반환하지 않는다. 만일 그것이 실패하면, 당신은 자식 프로세스가 종료되도록 무엇인가를 해야만 한다. 단지 return을 통하여 나쁜 상황 코드만을 반환하는것은 원래의 프로그램을 실행시키던 두개의 프로세스를 그냥 남기게 된다. 대신에, 올바른 처리는 부모 프로세스에게 자식 프로세스가 실패를 보고하는 것이다.

그것을 수행하도록 -exit를 호출하라. exit 대신에 _exit를 사용하는 이유는 stdout와 같은 완전히 버퍼화된 스트림들을 플러쉬 하는 것을 피하기 위함이다. 아마도 데이터를 담고 있는 그들 스트림의 버퍼들은 fork에 의해 부모 프로세스로 부터 복사된 것이고, 데이터는 부모 프로세스에 의해 결국 출력될 것이다. 자식 프로세스에서 호출된 exit는 데이터를 두 번 출력할 것이다. 22. 3. 5절 [Termination Internals] 참조.

크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기
Posted by 소리나는연탄.
TAGS ,

Leave your greetings here.

  
  
  
  
  
  
  
  
 

 

1. BASIC UNIX/SOCKET API
기본적인 UNIX 시스템의 CALL들은 알아야 한다.
1) 각 콜들이 무엇을 하는지, - 콜들의 역할
2) 어떻게 호출되는지, - 콜들의 인수
3) 데이터는 어떤 것들이 사용되며 무슨 의미를 가지는지. - 인수/결과값의
의미, 역할

2. HOW TO USE BASH
기본적인 SHELL SCRIPT의 사용법을 익힌다.
1) 변수지정
2) 환경변수와 변수의 차이 이해
3) 텍스트 조작 도구 습득: awk, grep, sed, cut, col 등
4) 정규 표현식/GLOB 이해 및 차이 이해

3. SYSTEM TRACING TOOL
시스템 상태를 알 수 있는 도구에 대한 이해
1) ps
2) df
3) du
4) lsof
5) strace

4. FILE MANAGEMENT
파일 관리에 대한 이해
1) 파일 생성 도구: cat/vi, mkdir
2) 파일 이름 변경: mv
3) 파일 삭제: rm, rmdir
4) 파일 검색: find, locate, which

5. SYSTEM DATABASE
UNIX SYSTEM DB에 대한 이해
1) /etc/nsswitch.conf
2) /etc/passwd,shadow,group
3) /etc/hosts,revolv.conf
4) /etc/services
5) /etc/inittab
6) /etc/protocols

크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기
Posted by 소리나는연탄.
TAGS

Leave your greetings here.