태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.
블로그 이미지

haRu™'s Thinks

느리게 걷기, 천천히 생각하기, 그리고 한 발 뒤에서 바라 보기… by haruroh


'System Programming'에 해당되는 글 19건

  1. 2007.12.24 20. Non-Local Exits
  2. 2007.12.22 부록 C : Maintenance
  3. 2007.12.22 부록 B : Library Summary
  4. 2007.12.22 부록 A : Language Features
  5. 2007.12.22 27. System Configuration
  6. 2007.12.22 26. System Information
  7. 2007.12.22 25. 사용자와 그룹
  8. 2007.12.22 24. Job Control(작업 제어)
  9. 2007.12.22 23. Processes
  10. 2007.12.22 22. 프로세스의 시동과 종료(Process StartUp)

20. Non-Local Exits

목차 이전 : 19. 지역과 세계화 다음 : 21. 시그널 처리 


20 비-지역 탈출

때때로 당신의 프로그램이 호출한 함수의 깊게 내포된 곳에서 평소와는 다른 상황을 검출할 때, 당신은 제어수준의 밖으로 즉시 반환이 가능하게

되는 것이 좋을 것이다. 이 절은 그와같은 비-지역 탈출의 상황에서

어떻게 setjmp 와 longjmp를 사용할 수 있는지를 설명한다.


20. 1 비-지역 탈출들에 대한 안내

비-지역 탈출이 유용할 수 있는 상황에 대한 예제로서, 프롬프트 상에서 명령문으로 실행하는 "메인 루프"를 가진, 즉 상호 작용하는 프로그램이 있다고 가정하자. 그리고 "read"라는 명령문으로는 파일로부터 입력을 읽어서, 그것이 처리되는 동안 입력의 어휘와 구문을 분석한다고 가정하자. 그때 만일 저-수준 입력 에러가 검출되면, 어휘와 구문분석 각각을 만들고, 내포된 호출에 의해 검출된 에러상황을 명백하게 취급하는 모든 처리상황을 만들기보다는 "메인 루프"로 즉시 반환 가능하도록 하는 것이 유용할 것이다. ( 다른 한편으로, 만일 파일들을 닫거나, 버퍼나 다른 데이터 구조체를 해제하는 등의 그와같은 exit의 상황일 때 그들 상황에서 그들에게 할당된 실제적인 양을 소거한다면_보통 반환을 하고 그 자체가 소거되는 것이 더 적당할 수 있다, 왜냐하면 비-지역 탈출은 방해되는 상황들을 무시하고 그들에 연관된 전체 코드를 소거하기 때문이다. 선택적으로 당신은 "메인 루프"의 뒤나 또는 앞에서 명백하게 소거를 할 수 있다. )

어떤 방법에서, 비-지역 탈출은 함수로부터 반환을 하는 `return' 구문을 사용하는 것과 유사하다. 그러나 `return'은 오직 단일하게 호출된 함수로부터 빠져나와서 그것이 호출되었던 지점의 뒤로 호출을 넘기지만, 비-지역 탈출은 내포된 함수 호출의 여러 단계를 빠져나올 수 있다.

당신은 setjmp 함수를 호출해서 비-지역 탈출을 위한 반환지점을 확인하라. 이 함수는 object의 타입 jmp_buf안에 실행환경에 대한 정보를 저장한다. setjmp의 호출 후에도 프로그램의 실행은 정상적으로 계속되지만, 만일 한 exit가 그것에 부합되는 jmp_buf 오브젝트를 인수로 하여 longjmp를 호출함으로써 이 반환 지점보다 나중에 만들어졌다면 제어는 setjmp가 호출되었던 그 지점의 뒤로 넘겨진다. setjmp로 부터의 반환값은, 보통의 반환과 longjmp를 호출함으로 해서 만들어진 반환과의 구별하기 위해 사용되므로, setjmp는 보통 `if'문안에 나타난다.

위에 설명된 예제 프로그램이 어떻게 만들어지는지를 보여준다.

#include <setjmp. h>
#include <stdlib. h>
#include <stdio. h>
 
jmp_buf main_loop;
 
void
abort_to_main_loop (int status)
{
longjmp (main_loop, status);
}
 
int
main (void)
{
while (1) {
if (setjmp (main_loop))
puts("Back at main loop. . . . ");
else
do_command();
}
}
 
void
do_command(void)
{
char buffer[128];
if (fgets (buffer, 128, stdin) == NULL)
abort_to_main_loop (-1);
else
exit (EXIT_SUCCESS);
}
 
함수 abort_to_main_loop는 그것이 어디로부터 호출되었는지에 상관없이, 프로그램의 메인 루프로 제어를 즉시 돌려준다.
 
메인 함수 안의 제어의 흐름은 처음에 조금 이상한일이 발생할지도 모르지만, 그러나 그것은 실제로 setjmp에서 사용하는 보통의 관용이다. setjmp는 보통 0을 반환하므로, 그래서 조건절 "else"가 실행되어진다. 만일 abort_to_main_loop가 do_command의 실행 중에 어느 곳에서 호출되어지면, 그것은 메인에서 setjmp를 호출하는 것처럼 나타난다. 그래서 setjmp를 사용하는 일반적 양식은 다음과 같다:
 
if (setjmp (buffer))
/* 때이른 반환 후에 소거하려는 코드 */
. . .
else
/* 반환 지점을 설정한 후에 일반적으로 실행되어지는 코드 */
. . .


20. 2 비-지역 분기의 상세한 설명

여기에서는 비-지역 분기를 수행하기 위해 사용되는 함수들과 데이터 구조체에 대해서 설명한다. 이들 도구들은 `setjmp. h'에 선언되어 있다.

데이터 타입 : jmp__buf

오브젝트의 타입 jmp_buf는 비-지역 분기에 의해 되돌려질 상황 정보를 저장한다. jum_buf의 내용들은 반환하기 위한 지정된 장소를 알린다.

매크로 : int setjmp (jmp_buf state)

보통 호출되었을 때, setjmp는 state안에 현재 실행중인 프로그램의 상황에 대한 정보를 저장하고 0을 반환한다. 만일 longjmp가 이 state로 비-지역 분기를 수행하기 위해 나중에 사용된다면, setjmp 는 0이 아닌 값을 반환한다.

함수 : void longjmp (jmp_buf state, int value)

이 함수는 state에 저장된 상황으로 현재 실행을 되돌리고, 반환 지점을 만들었던 setjmp를 호출한 지점부터 실행을 계속한다. longjmp에 의한 setjmp로부터의 반환은 longjmp에 주어졌던 0이 아닌 인수값을 반환한다. (그러나 만일 값이 0으로 주어지면, setjmp는 0을 반환한다. )

setjmp와 longjmp의 사용에는 많이 모호하지만 중요한 제한들이 있다. 이들 제한의 대부분은 비-지역 분기가 C 컴파일 상에서 상당한 양의 신비한 힘(?)을 요구했고 이상한 방법으로 언어의 다른 부분들과 함께 영향을 끼칠 수 있기 때문에 발생한다.

setjmp함수는 함수정의가 없는 매크로이다, 그래서 당신은 `#undef를 사용하거나 또는 그것의 주소를 취하려 할 수가 없다. 그것에 더하여, setjmp를 호출하는 것은 오직 다음과 같은 경우에만 안전하다.

  • 선택이나 반복구문을 시험하기 위한 표현으로(`if' 또는 `while'와 같은)
  • 선택이나 반복구문을 시험하기 위한 표현으로 사용한 동등 또는 비교 연산자에서 사용된 피연산자의 하나로써. 그때 다른 피연산자는 정수 상수 표현이어야만 한다.
  • 선택이나 반복구문을 시험하기 위한 표현으로 사용한 단항연산자`!'의 피연산자로 사용할 때.
  • 표현식으로 자체로 사용할 때.

반환지점은 그들을 만들기 위해서 setjmp를 호출했던 그 함수가 계속 작동하고 있을때만 유용하다. 만일 당신이 이미 반환되어진 함수에서 만들었던 반환지점으로 longjmp를 호출하면, 예측할 수 없고 불행한 일들이 발생될 것이다.

당신은 longjmp의 인수로써 0이 아닌 값을 사용수도 있다. longjmp는 setjmp로부터의 반환값인 0 인수를 되돌려주기를 거부하므로, 갑자기 발생하는 실수들에 대항한 안전망으로써의 역할을 할 수도 있지만 좋은 프로그래밍 스타일은 아니다.

당신이 비-지역 분기를 수행할 때, 검색 가능한 오브젝트들은 longjmp가 호출되었을 때 그들이 가졌던 값이 무엇이든지 일반적으로 계속 유지한다. 그러나 자동 지역변수의 경우에는 setjmp가 호출된 이후에 변경된다. 즉 그들을 휘발성으로 선언하지만 않으면 그들은 변경되지 않는다.


20. 3 비-지역 분기와 신호

BSD 유닉스 시스템에서, setjmp와 longjmp는 블록된 신호들을 저장하고 반환한다; 21. 7절 [Blocking Signals] 참조. 그렇지만, POSIX. 1 표준은 setjmp와 longjmp가 블록된 신호들을 변경하지 않을 것을 요구하고, BSD처럼 행동하기 위한 함수들(sigsetjmp와siglongjmp) 두 개를 부가적으로 제공한다.

GNU 라이브러리에서 setjmp와 longjmp의 행동은 테스트 매크로에 의해 제어된다; 1. 3. 4절 [Feature Test Macros] 참조. GNU 시스템에서 디폴트로 사용하는 것은 BSD가 아니라 POSIX. 1 이다. 이 절에 있는 도구들은 헤더파일 `setjmp. h'에 선언되어 있다.

데이터 타입 : sigjmp__buf

이것은 블록된 신호들에 대한 상황 정보를 저장할 수 있다는 것을 제외하고는 jmp_buf와 유사하다.

함수 : int sigsetjmp (sigjmp_buf state, int savesigs)

이것은 setjmp와 유사하다. 만일 savesigs가 0이 아니면, 블록된 신호들의 집합은 state에 저장되고 만일 siglongjmp가 나중에 이 state를 가지고 수행되면 반환되어질 것이다

함수 : void siglingjmp (sigjmp_buf state, int value)

이것은 state인수의 타입이 다르다는 것을 제외하고는 longjmp와 유사하다. 만일 0이 아닌 savesigs 플래그를 사용했던 state를 설정하고 sigsetjmp를 호출하면, siglongjmp는 또한 블록된 신호들의 집합을 반환한다.


목차 이전 : 19. 지역과 세계화 다음 : 21. 시그널 처리 

'Programming > Unix Programing' 카테고리의 다른 글

20. Non-Local Exits  (0) 2007.12.24
부록 D : GENERAL PUBLIC LICENSE 2  (0) 2007.12.22
부록 C : Maintenance  (0) 2007.12.22
부록 B : Library Summary  (0) 2007.12.22
부록 A : Language Features  (0) 2007.12.22
27. System Configuration  (0) 2007.12.22
Comment 0 Trackback 0
Top

부록 C : Maintenance

목차 이전 :  부록 B 라이브러리 기능들의 요약 다음 : 부록 D. GPL2


죄송합니다. 제가 가진 문서에서 이 장이 손실 되었습니ㄷ.(ㅜ.ㅜ)

3장으로 카피가 되이 있습니다. 혹시 가지고 계신 분 공유 좀 부탁합니다.

'Programming > Unix Programing' 카테고리의 다른 글

20. Non-Local Exits  (0) 2007.12.24
부록 D : GENERAL PUBLIC LICENSE 2  (0) 2007.12.22
부록 C : Maintenance  (0) 2007.12.22
부록 B : Library Summary  (0) 2007.12.22
부록 A : Language Features  (0) 2007.12.22
27. System Configuration  (0) 2007.12.22
Comment 0 Trackback 0
Top

부록 B : Library Summary

목차 이전 :  A. 라이브러리에 있는 C 언어 기능들 다음 : C. 라이브러리 유지보수


부록 B 라이브러리 기능들의 요약

이 부록은 GNU C 라이브러리 안에서 공급되는 헤더파일 안에 선언된 기능들의 완전한 리스트이다. 각 엔트리는 그 기능이 표준인지 아니면 다른 것으로부터 왔는지를 표시하고, 그것을 어떻게 사용하는지에 대한 정보를 이 매뉴얼의 어느 부분에서 찾을 것인지를 알려준다.

 
### 잠깐만!!!

이 부록 B는 함수들의 리스트가 나와있습니다. 번역이 필요치 않아서 위의 한 문단만 번역하고 넘어갑니다.

 

☞ GLIBC 영문판 1. 09 버전일 경우에 529 에서 609 까지의 내용입니다


목차 이전 :  A. 라이브러리에 있는 C 언어 기능들 다음 : C. 라이브러리 유지보수

'Programming > Unix Programing' 카테고리의 다른 글

부록 D : GENERAL PUBLIC LICENSE 2  (0) 2007.12.22
부록 C : Maintenance  (0) 2007.12.22
부록 B : Library Summary  (0) 2007.12.22
부록 A : Language Features  (0) 2007.12.22
27. System Configuration  (0) 2007.12.22
26. System Information  (0) 2007.12.22
Comment 0 Trackback 0
Top

부록 A : Language Features

목차 이전 : 27. 시스템 구성 파라미터 다음 : B. 라이브러리 기능들의 요약


부록 A 라이브러리에 있는 C 언어 기능들

C 라이브러리에 의해서 실행되는 기능들 중 어떤 것은 C 언어 그 자체의 일부처럼 생각될 수 있다. 그 기능들은 라이브러리 매뉴얼이 아니라, C 언어 매뉴얼에서 문서화되어야 하지만, 우리는 아직 그 C 언어 매뉴얼을 만들지 않았기 때문에, 그 기능들을 이곳에서 설명할 것이다.


A. 1 내부적 일관성을 명백하게 테스트하기

당신이 프로그램을 만들 때, "불가능한" 에러나 기본 가설의 위반에 대해서 전략적인 위치에서 그들을 체크하는 것은 좋은 생각이다. 그 체크는 프로그램의 다른 부분들 사이에서 불화합으로 일어나는 문제들을 디버깅하는데 유익하다.

헤더파일 `assert. h'에 정의된, assert 매크로는 프로그램의 에러가 검출된 곳에서 에러메세지를 출력하는 동안 프로그램을 중지시키기에 편리한 방법을 제공한다. 당신이 당신의 프로그램을 디버깅하려할 때, 당신은 정의된 매크로 NDEBUG를 사용해서 재컴파일하면 assert 매크로에 의해서 수행됐던 에러 체크는 디버그하지 않을 수 있다. 이것은 기능무효(disable)로 체크된 프로그램 소스 코드는 변경하지 않는다는 것을 의미한다.

그러나 그 체크를 무효화(disabling)하는 것은 그 프로그램을 확연하게 느리게 만들지 않는 한 바람직하지 않다. 또한 누가 그 프로그램을 실행시키던지 발생할 수 있는 가능한 에러에 대해서 대비하도록 좀더 많이 에러를 체크해보는 것이 좋다. 똑똑한 사용자는 어떤 것 이 잘못되었을 때 그것을 지적하지 않고 무의미한 반환값을 갖는 것보다는 차라리 프로그램이 파손되는 것을 원할 것이다.

매크로 : void assert (int expression)

프로그램 안에서 그 expression이 그 순간 0이 아닌 값이 되는지를 검증한다. 만일 NDEBUG 가 정의되지 않으면, assert는 expression의 값을 테스트하는데, 그 값이 0이면, assert는 다음 형식처럼 메시지를 프린트한 후에 프로그램을 중지시킨다 ( 22. 3. 4절 [Aborting a Program] 참조)
 
`file': linenum: Assertion `expression' failed.
표준 에러 스트림 stderr에 메시지를 프린트한다 ( 7. 2절 [Standard Streams] 참조. ). 파일 이름과 라인 번호는 C 전처리기 매크로 __FILE__ 과 __LINE__ 어로부터 가져오고 그 파일이름과 라인번호는 assert가 쓰여져서 호출된 곳으로 정한다. 만일 전처리기 매크로 NDEBUG가 `assert. h'가 인클루드된 지점에서 정의되었다면, assert 매크로는 완전히 아무 것도 하지 않도록 정의된 것이 된다.
 
주의 : 만일 NDEUG가 영향력을 발휘하는 곳에서는 인수 expression 표현식도 평가되지 않는다. 그러므로 부작용이 있을 법한 인수를 넣고 assert를 사용하지 말아라. 예를 들어, assert(++i>0); 은 만일 NDEBUG가 정의된 곳에서는 i변수가 증가되지 않기 때문에 좋지 않다.
 
사용법 노트: assert 기능은 내부적 모순을 검출하기 위해서 설계되었다; 그러므로 사용자에 의한 부적당한 사용이나 무효한 입력을 보고하는데는 적당하지 않다.

매크로 assert에 의해 프린트된 진단 메시지에 있는 정보는, 당신의 프로그램을 사용하는 사용자에게 왜 입력이 무효한지 또는 왜 명령이 실행되지 않았는지를 알리는데는 유용하지 않지만, 프로그래머나 당신이, 프로그램에서 나타난 버그를 추적하는데 도움이 된다. 그러므로 당신은 예측할 수 없는 사건에 대한 에러메세지를 출력하는데 assert를 사용할 수 없다.

더 말하자면, assert에 무효한 입력이 주어진다면 당신의 프로그램은 중지된다_그것은 에러메세지를 출력한 후에 0이 아닌 상황(22. 3. 2절 [Exit Status] 참조. )으로 종료되거나, 또는 다른 명령을 읽거나 다음 입력 파일로 옮긴다. 프로그램에 나타나지 않은 버그 문제에 에러메세지를 출력하는 것에 대한 정보는 2. 3절 [Error Messages] 참조.


A. 2 가변인자 함수들

ANSI C 는 인수의 타입이나 개수를 다양하게 취할 수 있는 함수를 선언하기 위한 구문을 정의한다. ( 그와같은 함수들은 varargs 함수 또는 variadic 함수라고 부른다. ) 그렇지만, 언어 그 자체는 그와같은 함수들을 위한 메커니즘을 제공하지 않는다; 대신에, `stdarg. h'에 정의된 가변 인수 매크로들을 사용하는 것이다. 이 절은 어떻게 가변인자 함수들을 선언하고, 어떻게 사용하며, 호출할 것인지에 대해서 설명한다.

 
이식성 노트 : 많은 오래된 C 방언들은 `varargs. h'를 사용해서 다양한 개수의 변수를 정의하는 함수 메커니즘으로 유사한 것을 제공하지만, 호환성이 없다.

A. 2. 1 왜 가변인자 함수들이 사용되는가?

원래 C 함수들은 고정된 개수의 인수들을 취한다. 당신이 함수를 정의할 때, 당신은 각 인수의 데이터 타입을 정한다. 함수가 호출될때마다 그전에 정해진 예상된 개수의 인수들이 공급되는데, 그 인수들의 타입은 정해진 것으로 변경될 수 있는 것이다. 그래서, 만일 함수 `foo'가 foo(int, char *); 로 선언된다면, 하나는 숫자 다른 하나는 문자열 포인터인 두 개의 인수를 가지고 foo 함수를 호출해야만 한다.

그러나 어떤 함수들은 정해지지 않은 개수의 인수를 받아들일 수 있는 동작을 수행한다. 어떤 경우, 함수는 한 블록에 그들의 모두를 처리함으로써 여러 개수의 값들을 처리할 수 있다. 예를 들어, 정해진 값들의 집합을 저장하기 위해서 malloc으로 일차원 배열을 할당하는 함수를 고려해보자. 이 연산은 숫자에 해당하는 배열의 길이로써 어떤 개수의 값이 있다고 이해한다. 가변 인수 기능이 없다면, 당신은 가능한 배열 크기를 얻어내는 또 다른 한 개의 함수를 정의해야만 한다.

라이브러리 함수 printf ( 7. 9절 [Formatted Output] 참조. )는 가변인수가 유용하게 쓰이는 다른 부류의 함수에 대한 예제가 된다. 이 함수는 규정된 템플리트 문자열의 제어 하에 인수들( 개수뿐만 아니라 다양한 형을 가질 수 있다)을 프린트한다. 가변인자 함수는 많은 인수들을 처리할 수 있다는 점에서 가변인수 함수를 정의하는 이유가 된다.

open과 같은 함수들은 고정된 개수의 인수들을 취하지만, 때때로 마지막 몇 개는 무시된다. ANSI C 는 그 함수를 가변으로 정의하도록 요구하지만; GNU C 컴파일러와 대부분 다른 C 컴파일러들을 고정된 인수를 취하는 함수처럼 정의하도록 허용하고 선언할 때만 가변으로써 그 함수를 선언한다 (또는 그 인수들을 전혀 선언하지 않는다. ).


A. 2. 2 어떻게 가변인자 함수를 정의하고 사용하는가?

가변인자 함수를 정의하고 사용하는 세 가지 단계이다.

인수리스트안에 생략표시 (`. . . ')를 사용하고, 가변 인수들을 억세스 하도록 특별한 매크로들을 사용하여서, 가변인수 함수를 정의하라. A. 2. 2. 2절 [Receving Arguments] 참조.

그것을 호출하는 모든 파일에서, 생략표시 (`. . . ')와 함께 프로토타입을 사용해서 가변으로써 함수를 선언하라. A. 2. 2. 1절 [Varidic Prototypes] 참조.

고정된 인수들 뒤에 가변인수들이 뒤에 나오도록 해서 함수를 호출하라. A. 2. 2. 4 [Calling Variadics] 참조.


A. 2. 2. 1 가변 인수들을 위한 구문

가변 인수를 받아들이는 함수는 올바른 프로토타입으로 선언되어야만 한다. 당신은 보통 고정된 인수들을 사용하고 가변인수들의 가능성을 지적하기 위해서 `. . . '을 취한다. ANSI C 구문은 `. . . '가 나오기 전에 적어도 한 개의 고정 인수를 필요로 한다. 예를 들어,

int
func (const char *a, int b, . . . )
{
. . .
}

고정된 두 개의 인수로써, const char * 와 int 인수를 취하고 int형의 값을 반환하는 func 함수의 정의이다. 그 두 개의 고정인수 다음에 알려지지 않은 인수들이 몇 개가 따르게 된다.

 
이식성 노트 : 어떤 C 컴파일러에서, 함수 정의에서 가변인수는 형을 선언하여 등록될 수 없다. 좀더 자세히 말하면, 이 인수들의 타입은 자체-진행(self-promoting)이 되어야만 한다: 즉, 디폴트 진행은 그 타입들을 변경하지 않아야 한다. 이것은, float , char(부호가 있던지 없던지), 그리고 short int (부호가 있거나 없거나) 뿐만 아니라 배열과 함수들의 타입을 무시한다.

A. 2. 2. 2 인수 값들을 받기

보통 고정된 인수들은 개별적인 이름을 갖고, 당신은 그들의 값을 억세스하기 위해서 그들의 이름을 사용할 수 있다. 그러나 가변 인수들은 아무런 이름을 갖지 않는다. 어떻게 당신이 그들을 억세스 할 것인가? 그들을 억세스하기 위한 유일한 방법은 그들이 기록된 순서대로, 순차적으로 억세스하고 다음 세 가지 단계에서 있는 헤더파일 `stdarg. h'에 선언된 특별한 매크로들을 사용해야만 한다.

1. va_start를 사용해서 va_list 형의 포인터 변수를 인수로써 초기화한다. 초기화된 인수 포인터는 첫 번째 가변인수를 가리킨다.
2. va_arg를 호출함으로써 가변인수들을 억세스 한다. va_arg를 첫 번째 호출하면, 첫 번째 인수를 반환하게 되고, 다음 호출은 두 번째 인수를 반환하고. . . 그렇게 진행이 된다.

당신이 만일 남겨진 가변인수를 무시하기를 원한다면 언제든지 멈출 수 있다. 호출로 공급된 인수들보다는 소수의 인수들을 억세스 하는 함수를 위해서 아주 좋지만, 만일 당신이 너무 많은 인수들을 억세스 하려 시도한다면 당신은 쓰레기 값을 얻게 될 것이다.

3. va_end를 호출해서 포인터 변수인 인수를 끝냈음을 알려라.

(실제로, 대부분 C 컴파일러에서, va_end의 호출은 아무 일도 하지 않고 당신은 그것을 실제로 호출할 필요가 없다. 이것은 GNU C 컴파일러에서는 항상 참이다. 그러나 누군가 당신의 프로그램을 독특한 컴파일러에서 컴파일하는 경우라면 va_end를 호출해야만 할 것이다.

va_start, va_arg 그리고 va_end에 대한 완전한 정의는 A. 2. 2. 5절 [Argument Macros] 를 참조하라. 단계 1과 3은 가변 인수를 받아들이는 함수에서 반드시 수행되어야만 한다. 그렇지만, 당신은 다른 함수에 인수로써 va_list 변수를 줄 수 있고 전부 또는 단계 2를 수행할 수 있다.

당신은 단일한 함수 호출에서 여러 번 세 단계의 전부를 반복해서 수행 할 수 있다. 만일 당신이 가변 인수를 무시하기를 원한다면, 세단계를 하지 않을 수 있다. 만일 당신이 원한다면 포인터 변수인 한 개의 인수보다 더 많은 것을 가질 수 있다. 당신은 당신이 원할 때 va_start로 각 변수를 초기화 할 수 있고, 그러고 나면 당신이 원하는 각각의 포인터 인수를 추출할 수 있다. 각 포인터 변수인 인수는 인수 값들의 같은 집합을 통해서 진행되지만, 그것은 자신만의 페이스(pace)를 갖는다.

 
이식성 노트: 어떤 컴파일러로, 당신이 서브루틴(subroutine)에 인수로써 포인터 변수를 사용한다면, 당신은 서브루틴이 반환한 후에 같은 포인터 변수인 인수를 사용해서 기록하지 않아야만 한다. 완벽한 이식성을 위해서, 당신은 va_end에 그것을 주어야한다. 이것은 실제로 ANSI C의 권장사항이지만, 대부분 ANSI C 컴파일러는 다행이 상관없이 작업한다.

A. 2. 2. 3 어떻게 많은 인수들이 공급되는가?

가변 인수들의 타입과 개수를 알 수 있는 일반적인 방법은 없다. 그래서 누구든 그 가변인수가 얼마나 많은 인수들을 가졌고, 그것이 무슨 종류인지를 알아낼 수 있는 특별한 방법의 함수를 고안해야한다. 그것은 가변인수 함수의 호출 관습에 적당하게 정의되어야 하고, 그것에 근거하여 프로그램에서 가변 인수 함수를 호출해야 한다.

호출관습의 한가지는 한 개의 고정된 인수를 사용해서 가변인수의 개수를 공급하는 것이다. 이 방법은 공급된 가변 인수들이 모두 같은 형일 경우에 가능한 방법이다. 그와 유사한 방법으로는 가변인수가 공급될 가능성에 대한 정보를 한 비트에 담은, 비트 마스크가될 고정인수를 인수중에 하나로 갖는 것이다. 당신은 미리 선언된 시퀀스 안에 있는 비트들을 테스트할 수 있다; 만일 그 비트가 설정되면, 다음 인수의 값을 추출하는 것이고, 그렇지 않다면, 디폴트값을 사용하는 것이다. 고정된 인수는 가변 인수들의 개수와 타입, 이 둘을 지정하는 패턴으로써 사용될 수 있다. printf에서 형식화된 문자열 인수는 이것의 한 예가 된다. (7. 9. 7절 [Formatted Output Functions] 참조.)

다른 가능성은 마지막 가변 인수로써 "끝 표시"값을 사용하는 것이다. 예를 들어, 예측할 수 없는 포인터 인수들의 개수를 처리하는 함수가 있다면, 널 포인터는 인수 리스트의 끝을 지적할 것이다. (이것은 널 포인터가 함수에게 의미 있는 값이 아니라고 가정한다. ) execl 함수는 이 방법으로 작업한다; 23. 5절 [Executing a File] 참조.


A. 2. 2. 4 가변인수 함수들을 호출하기

당신이 가변인수 함수를 호출할 때 특정한 어떤 것을 써서는 안된다. 단지 괄호안에 보통, 콤마에 의해 분리된 인수들(가변으로써, 요청된 인수)만 사용하라. 그러나 당신은 프로토타입으로 그 함수를 선언함으로써 준비하고, 그 인수의 값들이 어떻게 변환되는지를 알아야만 한다.

원칙적으로, 가변으로써 정의된 함수들은 당신이 그들을 호출할 때마다 함수 프로토타입을 사용해서 가변이 되도록 선언되어야한다. (A. 2. 2. 1 [Variadic Prototypes] 참조. ) 이것은 함수가 가변 인수 또는 고정된 인수를 취하는지의 여부에 의존하여 함수에 인수 값들을 부여하는 다른 호출 관습을 가진 C 컴파일러 때문이다.

실제로, GNU C 컴파일러는 항상 당신이 가변인수 또는 요청된 인수를 사용하는지에 상관없이 같은 방법으로 인수형의 주어진 집합을 부여한다. 그래서, 인수들의 타입이 자체-진행인 동안, 당신은 그들의 선언을 안전하게 생략할 수 있다. 보통 가변함수를 위해서 인수의 형을 선언하는 것은 좋은 방법이고, 모든 함수들을 위해서는 물론 당연한 것이다. 그런데 몇 개의 함수는 그렇지 않은 경우가 있다_예를 들어, open과 printf

함수의 프로토타입이 가변인수들의 타입을 정하지 않았을 때, 가변인수 함수를 호출하면, 함수의 가변 인수 값들은 디폴트 인수 승급이 수행된다. 디폴트 인수 승급이란 char 또는 short int (부호가 있던지 없던지)의 형을 가진 오브젝트들은 int 나 unisgned int로 승급되고; float의 형을 가진 오브젝트들은 double로 승급되는 것을 말한다. 그래서, 가변인수에 char형의 값을 넣으면, 그것은 int로 승급되고, 그 함수는 va_arg(ap, int)과 함께 그것을 얻을 것이다.

고정 인수들은 보통 함수의 원형을 통해서 제어된다: 인수 표현식은 마치 그형의 변수로 할당되었던 것 선언된 인수의 형으로 변환된다.


A. 2. 2. 5 인수 억세스 매크로들

다음은 가변 인수들을 가져오기 위해서 사용되는 매크로에 대한 기술이다. 그 매크로들은 헤더파일 `stdarg. h'에 정의되어 있다.

데이터 타입 : va__list

va_list는 포인터 변수들인 인수를 위해서 사용된다.

매크로 : void va__start (va_list ap, last_required)

이 매크로는 현재 함수의 가변 인수들의 첫 번째를 가리키는 포인터 변수 ap를 초기화한다; lastrequired는 함수에 있는 마지막 고정인수가 되어야 한다. `varargs. h'에 있는 va_start의 정의를 변경하려면 A. 2. 3. 1 [Old Varargs] 를 참조하라.

매크로 : type va__arg (va_list ap, type)

va_arg 매크로는 다음 가변 인수의 값을 반환하고, 다음 인수를 가리키도록 ap의 값을 갱신한다. 그래서, va_arg의 성공적인 사용은 가변 인수들을 성공적으로 반환한다. va_arg에 의해 반환된 값의 타입은 호출에서 정했던 타입이다. type은 반드시 실제 인수의 타입과 매치되는 자체-승급 타입 (char나 short int 나 float가 아닌)이 되어야 한다.

매크로 : void va__end (va_list ap)

이것은 ap의 사용을 끝낸다. va_end 호출 후에, 다음에 같은 ap를 사용해서 va_arg를 호출하면 작업하지 않을 것이다. 당신은 같은 ap 인수를 사용하는 va_start를 호출했던 함수를 반환하기 전에 va_end를 호출해야만 한다. GNU C 라이브러리에서, va_end는 아무 일도 하지 않기 때문에 이식성의 이유가 아니라면 va_end를 호출할 필요가 없다.

A. 2. 3 가변인수 함수의 예제

다음은 인수들을 가변적인 개수로 받아들이는 함수에 대한 예이다. 함수의 첫 번째 인수는 반환된 결과와 합산된, 남겨진 인수들의 개수이다. 이 함수는 가변 인수 기능을 어떻게 사용하는지 설명하는데 충분하다.

#include <stdarg. h>
#include <stdio. h>
 
int
add_em_up (int count, . . . )
{
va_list ap;
int i, sum;
 
va_start (ap, count);
/* 인수 목록을 초기화하라. */
sum = 0;
for (i = 0; i < count; i++)
sum += va_arg (ap, int);
/* 다음 인수값을 얻어라. */
va_end (ap);
/* 정리하라. */
return sum;
}
 
int
main (void)
{
/* 이 호출은 16을 출력한다. */
printf ("%d\n", add_em_up (3, 5, 5, 6));
 
/* 이 호출은 55를 출력한다. */
printf ("%d\n", add_em_up (10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
return 0;
}

A. 2. 3. 1 오래된-형태의 가변인수 함수들

ANSI C 이전에, 프로그래머들은 가변함수들을 쓰기 위해서 완전히 다른 기능을 사용했었다. GNU C 컴파일러는 여전히 그것을 지원한다; 현재, 그것은 ANSI C가 여전히 일반적이지 않기 때문에, ANSI C 기능보다는 더 이식성이 있다. 오래된-형태의 가변인수 함수를 정의하고 있는 헤더파일은 `varargs. h'라고 불린다.

`varargs. h'를 사용하는 것은 `stdarg. h'를 사용하는것과 거의 같다. 가변인수 함수를 어떻게 호출하는지에 대한 것은 거의 다름이 없다; A. 2. 2. 4절 [Calling Variadics] 참조. 오직 유일한 차이는 그들을 정의하는 방법이다. 무엇보다도, 당신은 오래된 형태의 비- 프로토타입 구문을 사용해야만 한다. 다음처럼:

tree
build (va_alist)
va_dcl
{
두 번째로, va_start에 오직 한 개의 인수만을 넣어야한다. 다음처럼:
va_list p;
va_start (p);

오래된 형태의 가변인수 함수들을 정의하기 위해서는 특정한 매크로가 사용된다:

매크로 : va__alist

이 매크로는 가변인수 함수에 있는 고정 인수이름 목록을 나타낸다.

매크로 : va__decl

이 매크로는 가변인수 함수를 위한 인수들이나 또는 암묵적인 인수를 선언한다.

매크로 : void va__start (va_list ap)

`varargs. h'에 선언된 이 매크로는 현재 함수의 첫 번째 인수를 가리키는 포인터로 포인터 변수인 인수를 초기화한다.

다른 인수 매크로, va_arg 와 va_end는 `varargs. h' 와 `stdarg. h'의 것이 서로 같다; A. 2. 2. 5절 [Argument Macros] 참조. 동일한 컴파일 단위에서 `varargs. h'와 `stdarg. h'가 둘다 인클루드 되어서는 안된다; va_start가 서로 충돌하게 된다.


A. 3 널 포인터 상수

널 포인터 상수는 어느 실제 오브젝트를 가리키고 있는 것이 아니라는 것을 말한다. 당신은 void * 형을 가진 포인터 변수로 그것을 할당할 수 있다. 널 포인터 상수를 사용하기 위한 좋은 방법은 NULL을 사용하는 것이다.

매크로 : void * NULL

이것은 널 포인터 상수이다.
널 포인터 상수로써 0 또는 (void *)0을 사용할 수 있지만, NULL을 사용하는 것이 상수를 사용하는 목적으로 좀더 분명하게 만들어졌기 때문에 더 깨끗하다. 만일 당신이 함수 인수로써 널 포인터 상수를 사용한다면, 그 함수가 가진 프로토타입 선언과 완전히 맞도록 널 포인터 상수를 사용해야한다. 그렇지 않다면, 만일 목표 머쉰(machin)이 두 개의 다른 포인터 표현을 가진다면, 컴파일러는 그 인수를 위해 사용할 표현이 무엇인지 알지 못한다.
당신은 적당한 포인터 타입으로 상수를 캐스트(cast)함으로써 그 문제를 피할 수 있지만, 우리는 그보다는 당신이 호출할 함수를 위해서 프로토타입에 더할 것을 권장한다.

A. 4 중요한 데이터 타입들

C에서 두 개의 포인터를 뺀 결과는 항상 정수이지만, 정밀한 데이터 타입은 C 컴파일러에 따라 다르다. . 그처럼 데이터 타입에 따라서, sizeof의 결과 또한 컴파일러에 따라서 다르다. ANSI 는 그 두 개의 데이터 타입을 위해서 표준 이름을 정의하기 때문에, 당신은 이식성을 위해서 그 데이터 타입을 사용할 수 있다. 그들은 헤더파일 `stddef. h'에 정의되어 있다.

데이터 타입 : ptrdiff__t

이것은 두 개 포인터를 빼서 나온 결과의 부호화된 정수 타입이다. 예를 들어, char *p1, *p2; 이렇게 선언되었다면; 표현식 p2 - p1은 ptrdiff_t 형의 결과값을 갖는다. 이것은 아마도 표준 부호형 정수 타입들(short int, int 또는 long int)중의 하나가 될 것이지만, 표준 부호형 정수 타입들이 이러한 목적으로 존재한다면 비표준 타입이 되어버릴 것이다.

데이터 타입 : size__t

이것은 오브젝트(objects)의 크기를 나타내는데 사용하는 비부호형 정수 타입이다. sizeof 연산의 결과가 이 타입을 갖고, malloc( 3. 3절 [Unconstrained Allocation] 참조. ) 그리고 memcpy(5. 4절 [Copying and Concatenation] 참조. )와 같은 함수들은 오브젝트의 크기를 정하는데 이 타입의 인수를 받아들인다.
사용법 노트: size_t는 오브젝트의 크기를 저장하는 인수나 변수를 선언하는데 좋은 방법을 제공한다.

GNU 시스템에서 size_t는 unsigned int 또는 unsigned long int 와 동일하다. 그 타입들은 GNU 시스템상에서 동일한 특성을 갖고 있고, 그들을 사용할 때 대부분은 그들 사이를 서로 변경시키지 않고도 사용할 수 있다. 그렇지만, 그들은 어떤 구문들에서는 차이를 갖기 때문에 다른 데이터타입으로 구분된 것이다.

예를 들어, 함수의 원형으로 함수 인수의 타입을 정할 때, 당신이 사용하는 것은 차이가 있다. 만일 시스템 헤더파일이 size_t 타입의 인수를 갖는 malloc 함수를 선언하고, 당신이 unisgned int의 타입을 갖는 malloc를 선언했을 때, 만일 size_t가 당신의 시스템에서 unisgned long int형으로 발생한다면, 당신은 컴파일 에러를 얻을 것이다. 이러한 문제의 가능성을 피하기 위해서, 함수의 인수나 값은 다른 방법으로 그 형을 선언하기보다는 size_t의 타입을 갖도록 선언하라.

 
호환성 노트 : ANSI C가 나타나기 전에 C는 포인터 뺄셈의 결과를 나타내기 위해서 int를 사용하고 오브젝트의 크기를 표현하기 위해서 unsigned int를 사용했었다. 그들은 size_t나 ptrdiff_t를 정의할 필요가 없었다. 유닉스 시스템들은 `sys/types. h'에 size_t를 정의해놓았지만, 그 정의는 보통 signed 형을 말한다.

A. 5 데이터 타입 측정

당신이 당신의 프로그램에서 사용되는 오브젝트의 적당한 C 데이터 타입을 선정할 때 대부분 그것이 얼마나 많은 비트들을 사용하고 그 오브젝트가 어떻게 표현되는지에 관심을 가질 필요가 없다. 당신이 그와같은 정보를 필요로 할 때, C 언어 자체는 그것을 얻을 수 있는 방법을 제공하지 않는다.

헤더파일 `limits. h'와 `float. h'에 포함된 매크로들은 당신에게 그것에 관한 세심한 정보를 줄 것이다.


A. 5. 1 정수 데이터 타입의 너비 계산하기

정수 타입이 얼마나 많은 비트로 구성되었는지 알 필요가 있는 프로그램은 비트 벡터(bit vector)로써 lont int의 배열을 사용하는 경우가 일반적이다.

당신은 vector[n / LONGBITS] & (1 << (n % LONGBITS)) 로 인덱스 N을 구성하는 비트를 억세스할 수 있다. LONGBITS는 long int를 구성하는 비트의 개수로 당신이 정의해서 공급하라.

C 언어에서 정수 데이터 타입에 있는 비트의 수에 대한 정보를 당신에게 줄 수 있는 연산자는 없다. 그렇지만 헤더파일 `limits. h'에 정의된 매크로 CHAR_BIT를 사용해서 그것을 계산할 수 있다.

CHAR_BIT

이것은 한 개의 char안에 있는 비트의 개수를 말하는데, 대부분의 시스템에서 그 값은 8이다. 그 값은 int형을 갖는다. 당신은 다음처럼 어느 데이터 타입 type안의 비트의 개수를 계산할 수 있다

A. 5. 2 정수 타입의 범위

당신이 0에서 일 백만 사이에 있는 정수의 값을 저장할 필요가 있다고 가정해보자. 당신이 사용할 수 있는 가장 작은 타입은 무엇인가? 그것을 정하는데 일반적인 규칙은 없다; 그것은 C 컴파일러와 목표 머쉰(machine)에 의존한다. 당신은 타입을 결정하기 위해서 `limits. h'에 있는 매크로 `MIN'과 `MAX'를 사용할 수 있다.

각각의 부호가 있는 정수 타입은 그것이 저장할 수 있는 가장 작은 값과 가장 큰 값을 나타내는 한 쌍의 매크로를 갖는다. 부호가 없는 정수 타입은 최대의 값을 나타내는 한 개의 매크로를 갖는다; 최소값은 물론 0이다. 그 매크로들의 값은 모두 정수 상수 표현이다. 다른 타입들을 위한 `MAX' 와 `MIN' 매크로들은 매크로에 의해 설명된 같은 타입의 값을 갖는다_그래서 ULONG_MAX는 unisgned long int 의 타입을 갖는다.

SCHAR_MIN

signed char에 의해 표현될 수 있는 최소값.

SCHAR_MAX, UCHAR_MAX

signed char 와 unsigned char에 의해 표현될 수 있는 최댓값.

CHAR_MIN

char 에 의해 표현될 수 있는 최소값. 여기서 만일 char에 부호가 있는 것이라면 SCHAR_MIN과 같고, 그렇지 않다면 0이다.

CHAR_MAX

char 에 의해 표현될 수 있는 최댓값. char가 부호가 있는 것이라면 SCHAR_MAX와 같고, 그렇지 않다면 UCHAR_MAX와 같다.

SHRT_MIN

signed short int에 의해 표현될 수 있는 최소값. GNU C 라이브러리가 실행되는 대부분의 컴퓨터에서, short int 는 16-비트로 동일하다.

SHRT_MAX, USHRT_MAX

signed short int 와 unsigned short int에 의해 표현될 수 있는 최댓값.

INT_MIN

signed int 에 의해 표현될 수 있는 최소값. GNU C 시스템이 실행되는 대부분의 컴퓨터에서, int 32-비트로 동일하다.

INT_MAX, UINT_MAX

signed int 와 unsigned int 에 의해 각각 표현될 수 있는 최댓값들.

LONG_MIN

signed long int 에 의해 표현될 수 있는 최댓값. GNU C 시스템이 실행중인 대부분의 컴퓨터에서, long 정수들은 int와 동일한 크기를 갖고 32-비트로 동일하다.

LONG_MAX, ULONG_MAX

signed long int 와 unsigned long int에 의해 표현될 수 있는 최댓값.

LONG_LONG_MIN

signed long int에 의해서 표현될 수 있는 최소값. GNU C 시스템이 실행되는 대부분의 컴퓨터에서, long long 정수들은 64-비트로 동일하다.

LONG_LONG_MAX, ULONG_LONG_MAX

signed long long int 와 unsigned long long int에 의해 표현될 수 있는 최댓값.

WCHAR_MAX

wchar_t에 의해 표현될 수 있는 최댓값. 18. 4절 [Wide Char Intro] 참조.

헤더파일 `limits. h'는 또한 다양한 운영체제와 파일 시스템 제한들을 파라미터 화한 부가적인 상수들을 정의하고 있다. 그 상수들은 27장[System Configuration] 에 설명되어 있다.


A. 5. 3 부동형 매크로들

플로팅 포인트 수의 구체적인 표현 방법은 기계마다 다르다. 플로팅 포인트 수들은 내부적으로 근사치로써 표현되기 때문에, 플로팅 포인트 데이터를 다루기 위한 알고리즘은, 때때로 기계의 플로팅 포인트 표현에 대한 자세한 정밀도를 참작하는데 사용된다.

C 라이브러리에서 어떤 함수들은 이러한 정보를 필요로 한다; 예를 들어, 플로팅 포인트 숫자들을 읽거나 출력하는 알고리즘 (7장 [I/O on Stream] 참조) 과 삼각함수를 계산하기 위한 알고리즘 그리고 무리수 함수들을 위한 알고리즘들은 정확성의 상실이나 반올림-에러를 피하기 위해서 그 정보를 사용한다. 수학적인 분석 기술을 다루는 프로그램들은 에러 경계를 계산하거나 최소화하기 위해서 이 정보를 필요로 한다. 헤더파일 `float. h'는 당신의 기계에 의해서 사용되는 형식을 설명한다.


A. 5. 3. 1 플로팅 포인트 표기 개념

이 절은 플로팅 포인트 표현법을 설명하기 위한 용어들을 설명한다. 당신은 플로팅 포인트 숫자들을 표현하는 과학적인 표기 또는 지수적인 표기의 개념에 대해서 이미 친숙할 것이다. 예를 들어, 숫자 123456. 0은 가수가 1. 23456이고 베이스가 10으로 5승임을 가리키는, 1. 234546e+05의 지수적 표기로써 표현될 수 있다. 더 형식적으로, 플로팅 포인트 수의 내부적 표현은 다음의 파라미터로써 특징을 나타낼 수 있다.

부호는 -1 또는 1 이다.

지수를 위한 베이스(base) 또는 기수(radix)는 1보다 큰 정수이다. 이것은 특정 표기에 따라서 다른 상수이다.

베이스에 몇 승인지를 나타내는 수가 지수이다. 지수의 상한과 하한은 특정한 표기에 따라서 다른 상수이다.

때때로, 플로팅 포인트 수를 표현하고 있는 실제 비트들에서, 지수를 항상 unsigned로 표현되도록 만들기 위해서 그것에 상수를 더하여 부호의 의미를 부여한다. 이것은 만일 당신이 직접 플로팅 포인트 수를 구성하고 있는 비트 영역들의 어떤 부분을 사용할 필요가 있는 경우에만 중요하다. GNU 라이브러리는 이러한 것을 지원하지 않는다. 그러므로 다음부터는 이것에 대한 논의는 무시될 것이다.

가수부(mantissa) 또는 유효수(significand)는 플로팅 포인트 숫자를 이루는 한 부분으로써 부호가 없는 정수이다.

가수부의 정밀도. 만일 어떤 플로팅 표현에서 베이스(base)가 b라고 했을 때, 정밀도는 베이스-b를 기반으로 가수부 안에 들어가 있는 숫자들의 개수이다. (즉. . 가수부가 몇 개의 비트로써 표현되느냐를 정밀도라고 한다. ) 이것은 특정한 표현에 따라 다른 상수이다.

많은 플로팅 포인트 표현들은 가수부 안에 암묵적으로 숨겨진 비트를 가지고 있다. 이것은 가수부 안에서 실질적으로는 표현되지만 그 값이 항상 1로 되어있기 때문에 메모리에는 저장되지 않는다. 정밀도 형태는(위를 보라) 숨겨진 비트들도 표함 한다. 다시, GNU 라이브러리는 플로팅 포인트 표현을 위해서 그와같은 저수준의 관점을 다루는 기능을 제공하지 않는다.

플로팅 포인트 수의 가수부는 지수의 몇 승을 가진 함축적인 소수부로써 표현된다. 그래서 가장 크게 표현할 수 있는 가수부가 이 정밀도보다 적은 것이면, 소수의 값은 항상 1보다 적다. 플로팅 포인트 수의 수학적인 값은 소수, 부호 그리고 베이스의 몇 승임을 나타내는 지수로써 만들어진다.

b가 베이스라고 했을 때, 소수가 적어도 1/b라면, 플로팅 포인트 수가 일반화되었다고 말한다. 바꾸어 말하면, 가수부에 지수승이 곱해지면 맞추기에 너무 크게 될 것이다. 비-일반화된 수들은 디노멀(denomal)이라고 부른다; 그들은 플로팅 포인트 수가 일반적으로 저장될 수 있는 정밀도 보다도 작은 정밀도를 갖고 있다.

만일 그 수가 일반화되지 않았다면, 가수부를 base로 나눈 다음 나온 지수로부터 1을 뺄 수 있고, 그러면 같은값을 가진 다른 표기형식의 플로팅 포인트를 얻게 된다. 그 수가 일반화될 때까지 반복적으로 위와 같은 일을 하면 일반화된 플로팅 포인트 수가 나오게 된다. 두 개의 다른 일반화된 플로팅 포인트 수들은 값이 같을 수 없다.

( 이 규칙에는 예외가 있다: 만일 가수부가 0이라면, 그것은 일반화된 것으로 간주된다. 특정한 기계에서 발생할 수 있는 예외상황이란, 지수부가 그 표기법으로 저장할 수 있기에는 너무 작은 경우이다. 그러면 지수부로부터 1을 빼는 것이 불가능하기 때문에 , 소수가 1/b보다 적은 소수부라면 일반화될 수 있을 것이다. )


A. 5. 3. 2 플로팅 포인트 파라미터들

다음 매크로 정의들은 헤더파일 `float. h'에 있다. `FLT_'로 시작하는 매크로들은 float 타입에 관한 것이고, `DBL_'로 시작되는 매크로들은 double 타입에 와 `LDBL_'로 시작되는 매크로들은 long double 타입에 관한 것이다. (현재 GCC 는 분리된 데이터형으로써 long double를 지원하지 않기 때문에, `LDBL_'상수들을 위한 값들은 double형을 위한 상수에 해당되는 값과 같다. )

그 매크로들 중에서, 오직 FLT_RADIX는 상수 표현식이 되도록 보증된다. 이곳에 설명된 다른 매크로들은 상수 표현식, `#if'와 같은 전처리 지시자 또는 정적 배열 안의 차원을 요구하는 곳에서 사용될 수 없다.

ANSI C 표준이 대부분의 파라미터들을 위한 최소값과 최댓값 정했다고 하더라도, GNU C는 목표 기계의 플로팅 포인트 표현에 따른 값을 사용한다. 그래서 GNU C는 목표 기계가 안정적이라면 ANSI C 요구를 만족시키게 되는 것이다. 실제로, 현재 지원되는 모든 기계들은 안정적이다.

FLT_ROUNDS

이 값은 반올림 형식을 지정하는 값이다. 다음 값은 표준 반올림 모드를 나타낸다.
-1 반올림하지 않는다.
0 소수점 뒤를 0으로 만든다.
1 가장 가까운 수로 반올림한다.
2 무한대로 양의 값을 향한다.
3 무한대로 음의 값을 향한다.
이 이외의 값은 기계_의존적인 비표준 반올림 모드를 나타낸다. 대부분의 기계에서, 그 값은 IEEE 표준에 따라서 1로 되어있다.
다음은 FLT_ROUNDS의 값에 따라서 값들이 어떻게 변하는지를 보여주는 테이블이다,
0 1 2 3
1. 00000003 1. 0 1. 0 1. 00000012 1. 0
1. 00000007 1. 0 1. 00000012 1. 00000012 1. 0
-1. 00000003 -1. 0 -1. 0 -1. 0 -1. 00000012
-1. 00000007 -1. 0 -1. 00000012 -1. 0 -1. 00000012

FLT_RADIX

이것은 지수부의 베이스(base) 또는 기수(radix)의 값이다. 이것은 이 절에 설명된 다른 매크로와는 달리 상수 표현식임이 보장된다. IBM 360과 그곳에서 파생된 제품을 제외하고는 모든 기계에서 2로 되어있다.

FLT_MANT_DIG

float형에서 가수부를 표현하는데 사용되는 비트수. 다음 표현식은 가수부 숫자들의 제한된 수 때문에 1. 0이 나온다(수학적으로는 그것이 될 수 없을 지라도):
float radix = FLT_RADIX;
1. 0f + 1. 0f / radix / radix / . . . / radix
여기서 radix는 FLT_MANT_DIG 번 나타난다.

DBL_MANT_DIG

LDBL_MANT_DIG

이것은 각각 double 과 long double형 각각이 가수부를 표현하는데 사용되는 비트수이다.

FLT_DIG

This is the number of decimal digits of precision for the float data type. Technically, if p and b are the precision and base (respectively) for the representation, then the decimal precision q is the maximum number of decimal digits such that any floating point number with q base 10 digits can be rounded to a floating point number with p base b digits and back again, without change to the q decimal digits.
float형에서 유효숫자의 최소개수.
이 매크로의 값은 ANSI C에서, 적어도 6으로 지원되고 있다.

DBL_DIG, LDBL_DIG

FLT_DIG와 유사하지만, double와 long double형을 위한 것이다. 그 매크로의 값은 적어도 10이 되도록 지원된다.

FLT_MIN_EXP

이것은 float형을 위해서 가능한 지수값으로 가장 작은 값이다. 더 자세하게는, FLT_RADIX에서 1을 뺀 값이 float형으로써 일반화된 플로팅 포인트 수로써 표현될 수 있는 최소 음의 정수이다.

DBL_MIN_EXP, LDBL_MIN_EXP

FLT_MIN_EXP와 유사하지만, double 와 long double를 위한 것이다.

FLT_MIN_10_EXP

This is the minimum negative integer such that 10 raised to this power minus 1 can be representedas a normalized floating point number of type float. This is supposed to be -37 or even less.
지수부의 최소범위. -37이거나 그보다 적다.

DBL_MIN_10_EXP, LDBL_MIN_10_EXP

double와 long double형 각각을 위한 것으로, 지수가 나타낼 수 있는 최소범위.

FLT_MAX_EXP

This is the largest possible exponent value for type float. More precisely, this is the maximum positive integer such that value FLT_RADIX raised to this power minus 1 can be represented as a floating point number of type float.
float이 표현할 수 있는 지수의 최댓값.

DBL_MAX_EXP, LDBL_MAX_EXP

double 와 long double형이 각각 표현할 수 있는 지수의 최댓값.

FLT_MAX_10_EXP

This is the maximum positive integer such that 10 raised to this power minus 1 can be represented as a normalized floating point number of type float. This is supposed to be at least 37.
float형에서 베이스가 10일 때 표현할 수 있는 지수의 최댓값. 이것은 적어도 37이다.

DBL_MAX_10_EXP, LDBL_MAX_10_EXP

double 와 long double형엣 베이스가 10일 때 각각이 표현할 수 있는 지수의 최댓값.

FLT_MAX

이 매크로의 값은 float형이 표현할 수 있는 최대 수를 의미한다. 이것은 적어도 1E+37이 된다. 값 자체도 float형을 갖는다. 표현 가능한 가장 작은 수는 -FLT_MAX가 된다.

DBL_MAX, LDBL_MAX

doble 와 long double 형 각각이 표현할 수 있는 최대 수를 의미한다. 이 매크로 값이 가지는 형은 그것을 설명하는 형과 동일하다.

FLT_MIN

이 매크로의 값은 float형이 표현할 수 있는 표준화된 양의 플로팅 포인트 수의 최소값. 그 값은 1E-37보다 크지 않다.

DBL_MIN, LDBL_MIN

double 과 long double형 각각을 위한 것으로 표준화된 양의 플로팅 포인트수의 최소값. 매크로의 값 자체가 가지는 형은 그것이 설명하는 형과 동일하다.

FLT_EPSILON

1. 0 + FLT_EPSILON != 1. 0이 참인 float형의 플로팅 포인트 수의 최소 양의 수이다. 1E-5보다 크지 않다.

DBL_EPSILON, LDBL_EPSILON

double 와 long double를 위한 것으로 의미는 FLT_EPSILON과 같다. 이 매크로 값 자체가 가지는 형은 그것이 설명하는 형과 동일하다. 그 값은 1E-9보다 크지 않다.

A. 5. 3. 3 IEEE 플로팅 포인트

다음은 이진 플로팅 포인트 연산을 위해서 IEEE 표준에서(ANSI/IEEE Std 754-1985) 정한, 대부분의 일반 플로팅 포인트 표기에서 산출된 float형의 대부분의 매크로 값을 보여주고 있다. 1980년대 이후에 디자인된 대부분의 컴퓨터는 이 형식을 사용한다.

IEEE 단정도(single-precision) float 표기법은 베이스로 2를 사용한다. 그것은 23비트에 한 개의 숨겨진 비트를 더해서(그래서 총 정밀도는 베이스를 2로 했을 때 24가 된다. ) 부호 비트와 가수부를 나타내고, 8-비트 지수부는 -125에서 128까지의 범위에 있는 값을 표현할 수 있다. 다음은, float형 데이터를 이 표기법을 사용할 경우, 그것에 연관된 파라미터의 적당한 값을 나타내고 있다.

FLT_RADIX 2
FLT_MANT_DIG 24
FLT_DIG 6
FLT_MIN_EXP -125
FLT_MIN_10_EXP -37
FLT_MAX_EXP 128
FLT_MAX_10_EXP +38
FLT_MIN 1. 17549435E-38F
FLT_MAX 3. 40282347E+38F
FLT_EPSILON 1. 19209290E-07F

다음은 double 데이터 타입을 위한 값들이다.

DBL_MANT_DIG 53
DBL_DIG 15
DBL_MIN_EXP -1021
DBL_MIN_10_EXP -307
DBL_MAX_EXP 1024
DBL_MAX_10_EXP 308
DBL_MAX 1. 7976931348623157E+308
DBL_MIN 2. 2250738585072014E-308
DBL_EPSILON 2. 2204460492503131E-016

A. 5. 4 구조체 필드 옵셋 ( offset ) 측정

구조체 형안에서 특정한 구조체멤버의 위치를 계산하기 위해서는 offsetof를 사용할 수 있다.

매크로 : size_t offsetof (type, member)

이것은 구조체형을 가진 type안에 있는 member라는 이름을 가진 구조체 멤버의 옵셋(offset)을 구하는 정수 상수 표현식이다. 예를 들어, offsetof(struct s, elem)은 struct s라는 구조체 안에 있는 멤버 elem의 오프셋(offset)이 된다. 이 매크로는 만일 멤버가 비트 필드가 아니면 작업하지 않는다; 당신은 그 경우에 컴파일러로부터 에러를 얻게 될 것이다.

목차 이전 : 27. 시스템 구성 파라미터 다음 : B. 라이브러리 기능들의 요약

'Programming > Unix Programing' 카테고리의 다른 글

부록 C : Maintenance  (0) 2007.12.22
부록 B : Library Summary  (0) 2007.12.22
부록 A : Language Features  (0) 2007.12.22
27. System Configuration  (0) 2007.12.22
26. System Information  (0) 2007.12.22
25. 사용자와 그룹  (0) 2007.12.22
Comment 0 Trackback 0
Top

27. System Configuration

목차 이전 : 26. 시스템 정보 다음 : A 라이브러리에 있는 C 언어 기능들


27 시스템 구성 파라미터

이 장에 설명된 함수들과 매크로들은 운영체제의 구성 파라미터에 대한 정보를 제공한다_시스템 구성 파라미터는, 예를 들면, 용량제한, 선택적인 POSIX 특징의 존재, 실행 파일들을 위한 디폴트 경로(path) ( 27. 12절[String Parameters] 참조. )


27. 1 일반적인 용량 제한

POSIX. 1 과 POSIX. 2 표준은 시스템의 용량 제한들을 나타내는 파라미터의 개수를 정한다. 그 제한들은 주어진 운영체제에서 고정된 상수이거나, 아니면 기계마다 다양할 수 있다. 예를 들면, 어떤 제한된 값은 실행할 때나, 커널을 다시 재건할 때, 시스템 관리자에 의해서 구성될 수 있고, 이것은 응용 프로그램들의 재 컴파일이 요구되지 않는다.

다음의 제한 파라미터들은 파라미터에 단일한 제한값을 갖는, 고정된 시스템일 경우에만 'limits. h'에 정의되어 있다. 만일 시스템이 다른 파일시스템이나 혹은 다른 제한을 갖는 파일들을 허용한다면, 그 매크로는 정의되지 않는다; 특정한 시스템에서 특정한 시간에 적용되는 제한을 찾아내려면 sysconf를 사용하라. 27. 4절 [Sysconf] 참조.

그 파라미터 각각은 다른 이름의 매크로를 갖는데, 그것은 어떤 POSIX시스템에서 갖도록 허용되는, 최소한의 제한 값이 주어지는, `_POSIX'로 시작하는 문자열이다. 27. 5절 [Minimums] 참조.

매크로 : int ARG__MAX

만일 정의되면, exec부류 함수들에게 인수로 줄 수 있는 argv와 환경변수들의 결합된 최대길이를 나타냄. 그 최대길이는 불변이다.

매크로 : int CHILD__MAX

만일 정의되면, 어떤 시간에 같은 실제 사용자 ID로 존재할 수 있는 프로세스의 최대개수를 말함. 그 최대 개수는 불변이다.

매크로 : int OPEN__MAX

만일 정의되면, 단일한 프로세스가 동시에 개방할 수 있는 파일들의 최대개수를 말한다. 그 최대개수는 불변이다.

매크로 : int STREAM__MAX

만일 정의되면, 단일한 프로세스가 동시에 개방할 수 있는 스트림의 최대개수를 말한다. 그 최대개수는 불변이다. 7. 3절 [Opening Streams] 참조.

매크로 : int TZNAME__MAX

만일 정의되면, 시간대(time zone)의 불변하는 최대길이를 말한다. 17. 2. 6 [Time Zone Functions] 참조.

 

다음 제한 매크로들은 `limits. h'에 항상 정의되어 있다.

매크로 : int NGROUPS__MAX

한 프로세스가 가질 수 있는 부가적인 그룹 ID들의 최대개수. 이 매크로의 값은 실제로는 최댓값의 하한 경계이다. 그것은 당신이 얼마나 많은 부가적인 그룹 ID들을 가질 수 있는지를 알 수 있게 하지만, 어떤 특정한 기계는 그것보다 더 많은 것을 허용하기도 한다. 당신은 어떤 특정한 기계가 당신에게 더 많은 ID들을 허용할 수 있는지에 대해서 알아보기 위해서 sysconf 를 사용할 수 있다. ( 27. 4절 [Sysconf] 참조. )

매크로 : int SSIZE__MAX

타입 ssize_t 의 오브젝트 안에 넣을 수 있는 가장 큰 값. 효율적이게, 이것은 단일한 명령에서 읽거나 쓸 수 있는 바이트의 개수에 대한 제한이 된다. 이 매크로는 제한이 불변이기 때문에 모든 POSIX 시스템들에서 정의되어 있다.

매크로 : int RE__DUP__MAX

정규표현식의 `\{min, max\}'의 구성 안에 허용되는 반복의 최대개수. 이 매크로의 값은 실제로는 최댓값 하향 경계선이다. 그것은 당신이 얼마나 많은 반복을 할 수 있는지를 알리지만, 어떤 특정한 기계는 그것보다 더 많은 반복을 허용한다. 어떤 특정한 기계가 더 많은 반복을 허용하는지를 알기 위해서는 sysconf를 사용할 수 있다. (27. 4절 [Sysconf] 참조. ) sysconf의 값은 단지 하향 경계의 값을 말해주므로_더 큰 값으로 작업할 수 있다. POSIX. 2 시스템들에서는 그들에게 어떤 정해진 제한 값이 없다고 하더라도 항상 정의되어야 하기 때문에 모든 POSIX. 2에서 이 매크로는 항상 정의되어 있다.


27. 2 포괄적인 시스템 옵션들

POSIX 는 모든 POSIX 시스템들이 지원하지 않는 시스템-명세 옵션을 정의한다. 그 옵션들은 라이브러리가 아니라 커널에 제공되고 있기 때문에, 간단히 GNU C 라이브러리를 사용해서는 그 기능들이 지원됨을 보증할 수 없다; 당신이 사용중인 시스템에 달려있다.

이 절에 있는 매크로들과 sysconf 함수를 함께 사용해서 주어진 옵션의 유용성을 테스트할 수 있다. 매크로들은 `unistd. h'에 정의되어 있다. 다음 매크로들은, 만일 그 매크로들이 `unistd. h'에 정의되어있다면, 옵션들이 지원되는 것이고, 그렇지 않다면 옵션이 될 수도 있고 되지 않을 수도 있다. 알아내려면 sysconf를 사용하라. 27. 4절 [Sysconf]참조.

매크로 : int __POSIX__JOB__CONTROL

만일 이 심볼이 정의되면, 시스템이 작업제어를 지원함을 알린다. 그렇지 않다면, 한 세션에 있는 모든 프로세스들은 단일한 프로세스 그룹에 소속되어있다. 24장 [Job Control] 참조.

매크로 : int __POSIX__SAVED__IDS

만일 이 심볼이 정의되면, 실행파일이 set-user-ID 또는 set-group-ID 비트들을 설정하여 실행되기 전에 프로세스의 유효 사용자와 그룹 ID들을 시스템이 기억했다가 나중에 그 값들을 다시 반환함을 의미한다. 이 옵션이 정의되지 않으면, 만일 특권을 가지지 않은 프로세스가 그 유효 사용자와 그룹 ID 를 프로세스의 실제 사용자 또는 그룹 ID로 변경했을 때 그것을 다시 되돌릴 수 없다. 25. 8절 [Enable/Disable Setuid] 참조.

다음 매크로에서, 만일 그 매크로들이 `unistd. h'에 정의되면, 그 값은 그 옵션이 지원되는지의 여부를 알린다. -1의 값의 의미는 없음을, 제외한 다른 값이 있음을 의미한다. 만일 그 매크로가 정의되지 않았다면, 그 옵션은 지원될 수도 있고 안될 수도 있다; 알아내려면 sysconf를 사용하라. 27. 4절 [Sysconf] 참조.

매크로 : int __POSIX2__C__DEV

만일 이 심볼이 정의되면, 그것은 그 시스템이POSIX. 2 C 컴파일러 명령, c89를 가지고 있음을 알린다. GNU C 라이브러리는 만일 당신이 C 컴파일러를 가지지 않았다면 당신이 그것을 인스톨하지 않았다는 가정으로, 항상 그 값을 1로써 정의한다.

매크로 : int __POSIX2__FORT__DEV

만일 이 심볼이 정의되면, 시스템이 POSIX. 2 포트란 컴파일러 명령 fort77을 가지고 있음을 알린다. GNU C 라이브러리에서 이 값은 결코 정의되지 않는다.

매크로 : int __POSIX2__FORT__RUN

만일 이 심볼이 정의되면, 시스템이 포트란 캐리지 제어를 해석하기 위한 명령으로 POSIX. 2 asa 명령을 갖고 있음을 지적한다. GNU C 라이브러리는 그 시스템이 무엇을 가졌는지 알지 못하기 때문에 이 매크로는 결코 정의하지 않는다.

매크로 : int __POSIX2__LOCALEDEF

이 심볼이 정의되면, 시스템이 POSIX. 2 localedef 명령을 갖고 있음을 알린다. GNU C라이브러리는 그 시스템이 무엇을 가졌는지 알지 못하기 때문에 이 매크로는 결코 정의하지 않는다.

매크로 : int __POSIX2__SW__DEV

이 심볼이 정의되면, 시스템이 POSIX. 2 명령들 ar, make 그리고 strip을 가지고 있음을 알린다. GNU C라이브러리에서, ar과 make는 라이브러리를 인스톨시키기 위해서는 당신이 꼭 갖고 있어야만하고 strip이 없음은 가망 없는 일이므로 이 매크로 값을 항상 1로써 정의한다.


27. 3 지원되는 POSIX의 버전은?

매크로 : long int __POSIX__VERSION

이 상수는 POSIX. 1 표준의 버전을 나타낸다. 1990 POSIX. 1은 199009L의 값이다. _POSIX_VERSION은 항상 어느 POSIX 시스템에서든지 `unistd. h'에 정의되어 있다.
사용법 노트: `unistd. h' 를 인클루드 함으로써 시스템이 POSIX를 지원하는지의 여부를 테스트하지 말고 _POSIX_VERSION이 정의되었는지를 통해서 그 여부를 테스트하라. 비-POSIX 시스템은, `unistd. h'가 없기 때문에 아마도 실패할 것이다. 컴파일 시간에 POSIX가 지원되는지, 또는 `unistd. h'가 존재하는지의 여부를 당신이 알 수 있는 방법은 없다.

GNU C 컴파일러는 만일 목표 시스템이 POSIX 시스템이라면 심볼 __POSIX__를 미리 정의한다. 정의된 __POSIX__를 테스트해서, 당신이 POSIX 시스템상에서 사용하지 않는 것을 제공했는지, 그러한 것을 신뢰성 있게 검출할 것이다.

매크로 : long int __POSIX2__C__VERSION

이 상수는 라이브러리와 시스템 커널이 제공하는 POSIX. 2 표준의 버전을 나타낸다. 우리는 POSIX. 2 표준의 첫 번째 버전이 어떤 값인지 알지 못하기 때문에 그 값은 그 표준이 공식적으로 채택된 연도와 달을 기준으로 한다.
이 심볼의 값은 시스템에 인스톨된 유틸리티에 대한 것은 아무 것도 말하지 않는다.
 
사용법 노트: 당신은 POSIX. 1 시스템 라이브러리가 POSIX. 2를 지원하는지의 여부를 알기 위해서 이 매크로를 사용할 수 있다. 어떤 POSIX. 1 시스템이 `unistd. h'를 갖고있다면, 그 파일을 인클루드하고 정의된 (_POSIX2_C_VERSION) 을 테스트하라.


27. 4 sysconf 사용하기

당신의 시스템이 시스템 제한을 구성할 수 있을 때, 당신은 어느 특정한 기계에 적용되는 그 값을 알아내기 위해서 sysconf 함수를 사용할 수 있다. 그 함수와 연관된 파라미터 상수들은 헤더파일 `unistd. h' 에 선언되어 있다.

27. 4. 1 sysconf의 정의

함수 : long int sysconf (int parameter)

이 함수는 실행시간 시스템 파라미터에 대해서 질의하기 위해서 사용된다. 그 parameter 인수는 밑에 설명된 심볼들 `_SC_'중 하나가 되어야 한다. sysconf의 보통의 반환값은 당신이 요청한 값이고 만일 그 제한이 허용되지 않거나 에러가 발생한 경우에는 -1의 값을 반환한다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.
EINVAL parameter의 값이 유용하지 않다.

27. 4. 2 sysconf의 parameter를 위한 상수들

다음은 sysconf에 parameter 인수로써 사용하기 위한 기호상수들이다. 그 값들은 모두 정수 상수들이다 (좀더 특별하게 말하면, 열거형 값이다. )

    _SC_ARG_MAX : ARG_MAX 에 해당하는 파라미터에 대해 질의한다.

    _SC_CHILD_MAX : CHILD_MAX 에 해당하는 파라미터에 대해 질의한다.

    _SC_OPEN_MAX : OPEN_MAX에 해당하는 파라미터에 대해 질의한다.

    _SC_STREAM_MAX : STREAM_MAX에 해당하는 파라미터에 대해 질의한다.

    _SC_TZNAME_MAX : TZNAME_MAX에 해당하는 파라미터에 대해 질의한다.

    _SC_NGROUPS_MAX : NGROUPS_MAX 에 해당하는 파라미터에 대해 질의한다.

    _SC_JOB_CONTROL : _POSIX_JOB_CONTROL에 해당하는 파라미터에 대해 질의한다.

    _SC_SAVED_IDS : _POSIX_SAVED_IDS 에 해당하는 파라미터에 대해 질의한다.

    _SC_VERSION : _POSIX_VERSION에 해당하는 파라미터에 대해 질의한다.

    _SC_CLK_TCK

    CLOCKS_PER_SEC에 해당하는 파라미터에 대해 질의한다; 17. 1. 1 [Basic CPU Time] 참조.

    _SC_2_C_DEV : POSIX. 2 C 컴파일러 명령, c89를 시스템이 가졌는지에 대해서 질의한다.

    _SC_2_FORT_DEV : 시스템이 POSIX. 2 포트란 컴파일러 명령, fort77을 가졌는지 여부를 질의한다.

    _SC_2_FORT_RUN

    시스템이 포트란 캐리지 제어를 해석하기 위한 POSIX. 2 asa 명령을 가졌는지 여부를 질의한다.

    _SC_2_LOCALEDEF : 시스템이 POSIX. 2 localedef 명령을 가졌는지 질의한다.

    _SC_2_SW_DEV : 시스템이 POSIX. 2 명령들 ar, make 그리고 strip를 가졌는지 질의한다.

    _SC_BC_BASE_MAX : bc 유틸리티에 있는 obase의 최댓값에 대해서 질의한다.

    _SC_BC_DIM_MAX : bc 유틸리티에 있는 배열의 최대 크기에 대해서 질의한다.

    _SC_BC_SCALE_MAX : bc 유틸리티에 있는 스케일의 최대 값에 대해서 질의한다.

    _SC_BC_STRING_MAX : bc 유틸리티에 있는 문자열 상수의 최대크기에 대해서 질의한다.

    _SC_COLL_WEIGHTS_MAX

    지역(locale)에서 대조문자열을 정의하는데 사용될 수 있는 가중치의 최대수에 대해서 질의한다.

    _SC_EXPR_NEST_MAX

    expr 유틸리티를 사용할 때 괄호안에 중첩된 표현식의 최대개수에 대해서 질의한다.

    _SC_LINE_MAX

    POSIX. 2 텍스트 유틸리티들이 처리할 수 있는 텍스트 라인의 최대크기에 대해서 질의한다.

    _SC_EQUIV_CLASS_MAX

    지역(locale) 정의에 있는 LC_COLLATE 범주 `order'에 배정될 수 있는 가중치의 최대수에 대해서 질의한다. GNU C 라이브러리는 곧 지역(locale) 정의를 지원하지 않는다.

    _SC_VERSION : 라이브러리와 커널이 지원하는 POSIX. 1의 버전 번호에 대해서 질의한다.

    _SC_2_VERSION : 시스템 유틸리티들이 지원하는 POSIX. 2 의 버전번호에 대해서 질의한다.

    _SC_PAGESIZE

    기계의 가상적인 메모리 크기에 대해서 질의한다. getpagesize도 같은값을 반환한다.

 

27. 4. 3 sysconf의 예제

우리는 일단 당신이 관심을 갖고있는 파라미터에 대한 매크로 정의가 있는지의 여부를 테스트 해보고 없다면 그 다음 sysconf를 사용하기를 권장한다. 예를 들어, 작업제어가 지원되는지의 여부를 어떻게 테스트하는지에 대한 예제가 다음에 있다:

int
have_job_control (void)
{
#ifdef _POSIX_JOB_CONTROL
return 1;
#else
int value = sysconf (_SC_JOB_CONTROL);
if (value < 0)
/* 만일 시스템이 고정되어 있다면, 시도를 계속할 수 없다. */
fatal (strerror (errno));
return value;
#endif
}
 
다음은 수치적 제한의 값을 얻기 위한 방법이다.
int
get_child_max ()
{
#ifdef CHILD_MAX
return CHILD_MAX;
#else
int value = sysconf (_SC_CHILD_MAX);
if (value < 0)
fatal (strerror (errno));
return value;
#endif
}


27. 5 총괄적인 용량 제한들을 위한 최소값

다음은 시스템 제한 파라미터들을 위한 POSIX 최소 하향 한계들을 위한 이름들이다. 그 값들의 유의값(significance)은 당신이 사용하고 있는 시스템이 무엇이든지 상관없이 그 제한을 안전하게 사용할 수 있는 값들이다.

_POSIX_ARG_MAX

이 매크로의 값은 exec 함수에 사용할 수 있는 argv와 환경변수들의 결합된 최대길이를 위한 POSIX에 의해 허용되고 있는 대부분의 제한 값이다. 그 값은 4096이다.

_POSIX_CHILD_MAX

이 매크로의 값은 실제 사용자 ID마다 동시에 가질 수 있는 프로세스들의 최대 개수로써 POSIX에 의해 허용되고 있는 대부분의 제한 값이. 그 값은 6이다.

_POSIX_NGROUPS_MAX

이 매크로의 값은 프로세스당 가질 수 있는 부가적인 그룹 ID들의 최대 개수로써 POSIX에 의해 대부분 허용되고 있는 제한 값. 그 값은 0이다.

_POSIX_OPEN_MAX

이 매크로의 값은 단일한 프로세스에서 동시에 개방할 수 있는 파일들의 최대개수로써 POSIX에 의해 대부분 허용되고 있는 제한 값. 그 값은 16이다.

_POSIX_SSIZE_MAX

ssize_t 의 오브젝트에 저장될 수 있는 최댓값으로써 POSIX에 의해 대부분 허용되고 있는 제한 값. 그 값은 32767이다.

_POSIX_STREAM_MAX

이 매크로의 값은 단일한 프로세스가 동시에 개방할 수 있는 스트림의 최대개수로써 POSIX에 의해 대부분 허용되고 있는 제한 값. 그 값은 8이다.

_POSIX_TZNAME_MAX

이 매크로의 값은 시간대의 최대 길이로써 POSIX에 의해 허용되고 있는 대부분의 제한 값. 그 값은 3이다.

_POSIX2_RE_DUP_MAX

이 매크로의 값은 정규표현식의 `\{min, max\}'에서 사용되고 있는 숫자로써 POSIX에 의해 허용되고 있는 대부분의 제한 값. 그 값은 255이다.


27. 6 파일 시스템 용량의 제한

POSIX. 1 표준은 파일 시스템의 제한들을 나타내는 파라미터들의 개수를 한정하고 있다. 이것은 보통의 경우는 아니지만, 한 파라미터에 단일한 값을 갖는 고정된 시스템에서 가능한 것이다. 대부분의 시스템에서, 다른 최대 제한을 갖는 다른 파일 시스템 ( 그리고, 다른 파라미터들이나, 심지어 다른 파일들 )이 가능하다. 예를 들어, 다른 기계들로부터 파일 시스템들의 어떤 것을 마운트(mount) 하기 위해서 NFS를 사용한다면 매우 가능성 있는 일이다.

다음 매크로들 각각은 만일 그 파라미터들이 고정된 값을 갖는 시스템이라면 `limits. h'에 정의되어 있다. 만일 그 시스템이 다른 제한들을 갖는 파일시스템이나 파일들을 허용한다면, 그 매크로는 정의되지 않는다; 특정한 파일에 적용되는 제한을 알아내려면 pathconf 나 fpathconf를 사용하라. 27. 9절 [Patcconf] 참조. 각 파라미터들은 다른 매크로들을 갖는데, 그 매크로는 POSIX 시스템에서 갖도록 허용되는 제한의 최소값을 갖는 `_POSIX'로 시작되는 이름이다. 27. 8절 [File Minimums] 참조.

매크로 : int LINK__MAX

불변의 시스템은 주어진 파일을 위한 이름들의 개수를 제한한다. 9. 3절 [Hard Links] 참조.

매크로 : int MAX__CANON

불변의 시스템은 입력 편집이 가능할 때 한 라인의 텍스트의 양을 제한한다. 12. 3절 [Canonical or Not] 참조.

매크로 : int MAX__INPUT

불변의 시스템은 입력(input)으로써 미리 입력(typed)된 문자들의 총 개수를 제한한다. 12. 2 [I/O Queses] 참조.

매크로 : int NAME__MAX

불변의 시스템은 파일 이름 구성요소의 길이에 대해서 제한한다.

매크로 : int PATH__MAX

불변의 시스템은 전체 파일이름의 길이에 대해서 제한한다 (그것은 open과 같은 시스템 호출에 주어진 인수이다).

매크로 : int PIPE__BUF

불변의 시스템은 파이프에 자동적으로 쓰여질 수 있는 바이트의 개수에 대해서 제한한다. 만일 다중 프로세스가 동시에 같은 파이프에 기록하고 있다면, 다른 프로세스들로부터의 출력은 이 크기의 청크(chunks) 에 삽입될 것이다. 10장 [Pipes and FIFOs] 참조.

 

다음은 같은 역할을 하는 매크로의 다른 이름이다.

매크로 : int MAXNAMLEN

이것은 NAME_MAX 를 위한 BSD 이름이다. `dirent. h'에 정의되어 있다.

매크로 : int FILENAME__MAX

이 매크로의 값은 파일 이름 문자열의 최대 길이를 나타내는 정수 상수 표현이다. 이것은 `stdio. h'에 정의되어 있다. PATH_MAX와 달리, 이 매크로는 실제로 제한된 값이 없더라도 정의되어 있다. 그 경우, 그 값은 전형적으로 매우 큰 값으로 존재한다. GNU 시스템에서는 항상 그 경우이다.
사용법 노트: 파일 이름을 저장하기 위해서 배열의 크기를 FILENAME_MAX를 사용하지 말아라! 당신이 그렇게 배열을 크게 만드는 것은 가능하지 않다! 대신에 동적 할당을 사용하라( 3장 [Memory Allocation] 참조. )


27. 7 파일에 지원되는 선택적 기능들

POSIX는 시스템에서 파일들을 운영하기 위해서 호출하는 시스템-설정 옵션들을 정의한다. 어떤 시스템들은 그 옵션들을 지원하고 다른 것은 지원하지 않는다. 그 옵션들이 라이브러리가 아니라 커널에서 제공되기 때문에, GNU C 라이브러리를 사용해서 그 기능들 중 어느 것이 지원되는지 간단히 확인할 수 없다. 그들은 파일 시스템과 기계 사이에서 굉장히 다양할 수 있다.

이 절은 당신의 기계가 특별한 옵션을 지원하는지에 대한 여부를 테스트 할 수 있는 매크로를 설명한다. 만일 주어진 매크로가 `unistd. h'에 정의되어 있다면, 그 값을 통해서 해당하는 기능들을 제공하는지에 대해서 알 수 있다. (-1의 값은 없음을 지적하고; 그 외 다른 값은 있음을 지적한다. ) 만일 그 매크로가 정의되어 있지 않으면, 그것은 특별한 파일들이 그 기능을 지원할 수도 있고 지원하지 않을 수도 있음을 의미한다.

GNU C 라이브러리를 지원하는 모든 기계에서는 또한 NFS를 지원하기 때문에, 모든 파일 시스템들이 _POSIX_CHOWN_RESTRICTED 와 _POSIX_NO_TRUNC 기능들을 지원하는지에 대해서 일반적인 구문을 전혀 만들 수 없다. 그래서 GNU C 라이브러리에서 그 매크로들은 결코 정의되지 않는다.

매크로 : int __POSIX__CHOWN__RESTRICTED

이 옵션이 가능하다면, chown 함수는 특권이 없는 프로세스들이 그 프로세스의 유효 사용자 ID나 부가적인 그룹 ID중의 하나로 파일의 그룹 소유자를 변경하는 것만을 허용하도록 제한한다. 9. 8. 4절 [File Owner] 참조.

매크로 : int __POSIX__NO__TRUNC

만일 이 옵션이 효과가 있다면, NAME_MAX 보다 긴 파일 이름 구성요소들은 ENAMETOOLONG 에러를 발생한다. 그렇지 않다면, 너무 긴 파일이름 구성요소는 잘리고 만다.

매크로 : unsigned char __POSIX__VDISABLE

이 옵션은 터미널 디바이스인 파일에만 오직 유효하다. 만일 이 옵션이 효력을 갖는다면, 특정한 제어문자를 처리할 때 개별적으로 효력이 없게 할 수 있다. 12. 4. 9절 [Special Characters] 참조.

만일 그 매크로들 중 하나가 정의되어 있지 않다면, 그것은 그 옵션이 어떤 파일들에는 효과가 있고 어떤 파일들에는 효과가 없음을 의미한다. 특정한 파일에 대해서 문의하려면, pathconf 또는 fpathconf를 호출하라. 27. 9절 [Pathconf] 참조.


27. 8 파일 시스템 제한을 위한 최소값들

다음은 위에 설명된 파라미터들에 대한 POSIX 최소 하향 경계들을 나타내는 이름이다. 그 값들은 당신이 사용하고 있는 특정한 시스템에 상관없이 제한들을 안전하게 사용할 수 있다.

_POSIX_LINK_MAX

POSIX에 의해 대부분 허가되고 있는 제한 값 파일의 링크 개수의 최댓값이다. 이 상수 값은 8이다. 그래서, 당신은 시스템 제한을 시험하지 않고도 한 개의 파일을 위해서 항상 8개의 이름을 만들 수 있다.

_POSIX_MAX_CANON

POSIX에 의해 대부분 허가되고 있는 제한 값, 터미널 디바이스로부터 한 개의 정규 입력 라인에서 최대한 받아들일 수 있는 바이트의 개수이다. 이 상수의 값은 255.

_POSIX_MAX_INPUT

POSIX에 의해 대부분 허가되고 있는 제한 값, 터미널 디바이스 입력 큐(또는 선행입력 버퍼)에 들어갈 수 있는 최대 바이트의 개수이다 12. 4. 4절 [Input Modes] 참조. 이 상수의 값은 255이다.

_POSIX_NAME_MAX

POSIX에 의해 대부분 허가되고 있는 제한 값, 파일 이름 구성에 있는 바이트의 최대 개수이다. 이 상수의 값은 14이다.

_POSIX_PATH_MAX

POSIX에 의해 대부분 허가되고 있는 제한 값 파일 이름의 최대 바이트 개수이다. 이 상수의 값은 255이다.

_POSIX_PIPE_BUF

POSIX에 의해 대부분 제한되고 있는 제한 값, 한 파이프에 자동적으로 쓰여질 수 있는 바이트의 최대 개수이다. 이 상수의 값은 512이다.


27. 9 pathconf 사용하기

당신의 기계가 한 개의 파일 시스템 파라미터에 대해서 여러 개의 값을 갖는 다른 파일들을 허용할 때, 당신이 어느 특정한 파일에 적용되는 값을 찾아내기 위해서는 이 절에 설명된 파일들을 사용해야만 한다. 그 함수들과 그와 연관된 파라미터 인수를 위한 상수들은 헤더파일 `unistd. h'에 선언되어 있다.

함수 : long int pathconf (const char *filename, int parameter)

이 함수는 filename이라는 이름을 가진 파일에 적용되는 제한에 대해서 질의하는데 사용된다. parameter 인수는 밑에 설명된 `_PC_' 상수중 한 개가 된다.
 
pathconf로부터의 보통 반환되는 값은 당신이 요청한 값이다. 어떤 제한으로 강요할 수 없거나 에러가 발생한 경우에는 -1의 값을 반환한다. 전자의 경우에는 errno가 설정되지 않았고, 후자의 경우에는 문제가 발생된 원인을 지적하기 위해서 errno가 설정되었다. 그래서 이 함수를 안정적으로 사용하기 위한 유일한 방법은 그것을 호출하기 전에 errno를 0으로 저장해놓는 것이다.
 
파일 이름 구문 에러들 외에도( 6. 2. 3절 [File Name Errors] 참조. ), 다음의 errno는 이 함수를 위해서 정의되었다.

EINVAL

parameter값이 유효하지 않거나, 그 실행이 지정된 파일을 위해서 그 parameter를 지원하지 않는다.

함수 : long int fpathconf (int filedes, int parameter)

이것은 정보를 요청하기 위한 파일을 정함에 있어서, 파일이름을 사용하는 것이 아니라 개방된 파일 기술자를 사용한다는 점을 제외하고는 pathconf와 같다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EBADF : filedes 인수가 유효한 파일 기술자가 아니다.

EINVAL

parameter 의 값이 유효하지 않거나, 그 실행은 지정된 파일을 위해서 그 parameter를 지원하지 않는다.

다음은 pathconf 와 fpathconf에 parameter 인수로써 사용할 수 있는 기호 상수들이다. 그 값들은 모두 정수 상수들이다.

    _PC_LINK_MAX

    LINK_MAX 의 값에 대해서 질의한다.

    _PC_MAX_CANON

    MAX_CANON 의 값에 대해서 질의한다.

    _PC_MAX_INPUT

    MAX_INPUT의 값에 대해서 질의한다.

    _PC_NAME_MAX

    NAME_MAX의 값에 대해서 질의한다.

    _PC_PATH_MAX

    PATH_MAX의 값에 대해서 질의한다.

    _PC_PIPE_BUF

    PIPE_BUF의 값에 대해서 질의한다.

    _PC_CHOWN_RESTRICTED

    _POSIX_CHOWN_RESTRICTED의 값에 대해서 질의한다.

    _PC_NO_TRUNC

    _POSIX_NO_TRUNC의 값에 대해서 질의한다.

    _PC_VDISABLE

    _POSIX_VDISABLE의 값에 대해서 질의한다.


27. 10 유틸리티 프로그램 용량 제한들

POSIX. 2 표준은 유틸리티 동작에 적용되는 시스템 제한을 정해놓았고, 당신은 sysconf를 통해서 그 제한을 억세스할 수 있다. GNU C 라이브러리는 그 제한들을 위해서 매크로를 정의하고, 만일 당신이 그 값을 질의하면 sysconf는 그들의 값을 반환한다; 하지만 그 값들은 아무런 의미 없는 정보이다. 그들은 POSIX. 2가 허가하는 가장 작은 값들이다.

매크로 : int BC__BASE__MAX

bc 유틸리티를 지원하도록 보증되는 obase의 최댓값.

매크로 : int BC__SCALE__MAX

bc 유틸리티를 지원하도록 보증되는 scale의 최댓값

매크로 : int BC__DIM__MAX

bc 유틸리티를 지원하도록 보증되는 한 개의 배열 안에 들어갈 수 있는 요소들의 최대개수.

매크로 : int BC__STRING__MAX

bc 유틸리티를 지원하도록 보증되는 한 개의 문자열 안에 들어갈 수 있는 문자들의 최대개수.

매크로 : int BC__DIM__MAX

bc 유틸리티를 지원하도록 보증되는 한 개의 배열 안에 들어갈 수 있는 요소들의 최대 개수.

매크로 : int COLL__WEIGHTS__MAX

로케일(locale)을 위해서 대조문자열을 정의하는데 사용될 수 있는 가중치의 최댓값.

매크로 : int EXPR__NEST__MAX

expr 유틸리티에 의해서 괄호안에 중첩될 수 있는 표현식의 최대 개수.

매크로 : int LINE__MAX

텍스트-지향 POSIX. 2 유틸리티들이 지원할 수 있는 최대 텍스트 라인. (만일 당신이 그 유틸리티의 GNU 버전을 사용하고 있다면, 가상 메모리를 사용함으로써 강요된 것을 제외하고는 실제로 아무런 제한이 없지만, 그 라이브러리가 당신에게 이것에 대해서 알릴 수 있는 아무런 방법이 없다. )

매크로 : int EQUIV__CLASS__MAX

로케일(locale) 정의에서 LC_COLLATE 범주 `order' 키워드의 엔트리에 배정될 수 있는 가중치의 최댓값. GNU C 라이브러리는 곧 로케일 정의들을 지원하지 않는다.


27. 11 유틸리티 제한들을 위한 최댓값

_POSIX2_BC_BASE_MAX

POSIX. 2에 의해 허용되고 있는 대부분의 제한으로써, bc 유틸리티 안에 있는 obase의 최댓값. 그 값은 99.

_POSIX2_BC_DIM_MAX

bc 유틸리티 안에 있는 배열의 최대크기를 위해서 POSIX. 2에 의해서 대부분 허용되고 있는 제한 값. 그 값은 2048.

_POSIX2_BC_SCALE_MAX

bc 유틸리티 안에 있는 scale의 최대크기를 위해서 POSIX. 2에 의해서 대부분 허용되고 있는 제한 값. 그 값은 99.

_POSIX2_BC_STRING_MAX

bc 유틸리티 안에 있는 문자열 상수의 최대크기를 위해서 POSIX. 2에 의해서 대부분 허용되고 있는 제한 값. 그 값은 1000이다.

_POSIX2_COLL_WEIGHTS_MAX

로케일 을 위해서 대조 문자열을 정의하는데 사용될 수 있는 가중치의 최댓값으로 POSIX. 2에 의해서 대부분 허용되고 있는 제한 값. 그 값은 2이다.

_POSIX2_EXPR_NEST_MAX

expr 유틸리티를 사용할 때 괄호안에 중첩된 표현식의 최대개수로써 POSIX. 2에 의해서 대부분 허용되고 있는 제한 값. 그 값은 32이다.

_POSIX2_LINE_MAX

텍스트 유틸리티들이 처리할 수 있는 텍스트 라인의 최대 크기로써 POSIX. 2에 의해서 대부분 허용되고 있는 제한 값. 그 값은 2048이다.

_POSIX2_EQUIV_CLASS_MAX

로케일 정의에서 LC_COLLATE 범주 `order' 키워드 엔트리에 배정할 수 있는 가중치의 최댓값으로써, POSIX. 2에 의해서 대부분 허용되고 있는 제한 값. 그 값은 2. GNU C 라이브러리는 곧 로케일 정의를 지원하지 않는다.


27. 12 문자열-평가 파라미터들

POSIX. 2는 confstr 함수를 사용해서 운영체제로부터 문자열-평가 파라미터들을 얻는 방법을 정의한다.

함수 : size_t confstr (int parameter, char *buf, size_t len)

이 함수는 buf에서 시작되는 메모리 공간에 len길이의 바이트를 가진 문자열을 저장하여, 문자열-평가 시스템 파라미터를 읽는다. parameter 인수는 밑에 설명된 `_CS_'중의 하나가 될 것이다.
 
confstr로부터 보통 반환되는 값은 당신이 요청했던 문자열 값의 길이다. 만일 당신이 buf를 널 포인터로 하여 그 함수를 호출한다면, confstr은 그 문자열을 저장하려 시도하지 않는다; 그것은 단지 그 길이를 반환한다. 에러를 지적하는 값은 0이다.
 
만일 당신이 요청했던 문자열이 버퍼보다 길다면(그것은, len-1 보다 길다), confstr은 단지 그 만큼( 널 종료 문자를 위한 공간은 남기고. )만 저장한다. 당신은 confsr이 len보다 크거나 같은값을 반환했을 때 그와같은 일이 발생했다는 것을 알 수 있다.
 
다음의 errno는 이 함수를 위해 정의된 에러상황이다.
EINVAL : parameter 값이 유효하지 않다.

다음은 confstr로 읽을 수 있는 단지 한 개의 파라미터이다.

_CS_PATH

이 파라미터의 값은 실행파일을 찾을 때 디폴트 경로에서 찾도록 한다. 이것은 로그인한 후에 사용자가 디폴트로 갖는 경로가 된다.
문자열 크기에 대한 제한 없이 confstr을 사용하는 방법은 그것을 두 번 호출하는 것이다. 첫 번째 호출은 길이를 얻고, 그것에 근거하여 버퍼를 할당하고 다시 confstr을 호출해서 버퍼를 채워라. 다음과 같이.
char *
get_default_path (void)
{
size_t len = confstr (_CS_PATH, NULL, 0);
char *buffer = (char *) xmalloc (len);
 
if (confstr (_CS_PATH, buf, len + 1) == 0)
{
free (buffer);
return NULL;
}
 
return buffer;
}


목차 이전 : 26. 시스템 정보 다음 : A 라이브러리에 있는 C 언어 기능들

'Programming > Unix Programing' 카테고리의 다른 글

부록 B : Library Summary  (0) 2007.12.22
부록 A : Language Features  (0) 2007.12.22
27. System Configuration  (0) 2007.12.22
26. System Information  (0) 2007.12.22
25. 사용자와 그룹  (0) 2007.12.22
24. Job Control(작업 제어)  (0) 2007.12.22
Comment 0 Trackback 0
Top

26. System Information

목차 이전 : 25. 사용자와 그룹 다음 : 27. 시스템 구성 파라미터


26 시스템 정보

이 장은 사용하고 있는 특정한 기계_즉 하드웨어의 타입, 소프트웨어의 타입, 그리고 개별적인 기계들의 이름_에 대한 정보를 반환하는 함수를 설명한다.


26. 1 호스트 확인

이 절은 당신의 프로그램이 실행되고 있는 특정한 기계(machine)를 어떻게 확인할 것인지를 설명하고 있다. 기계의 구분은 인터넷 호스트 이름(Internet host name) 과 인터넷 주소(Internet address)로 한다; 그 함수들에 대한 프로토타입은 `unistd. h'에 나와있다. 쉘 명령어 hostname과 hostid는 그들을 호출하여 작업을 수행한다.

함수 : int gethostname (char *name, size_t size)

이 함수는 배열 name에 호스트 기계의 이름을 반환한다. size인수는 이 배열의 크기를 바이트 단위로 지정한다.
 
이 함수는 성공하면 0을 반환하고, 실패하면 -1을 반환한다. GNU C 라이브러리에서, gethostname은 만일 size 인수가 충분히 크지 않다면 실패하기 때문에 그 경우에는 그 배열을 크게해서 다시 그 함수를 호출하라.
다음의 errno는 이 함수를 위해 정의된 에러상황이다.

ENAMETOOLONG

size 인수가 호스트 이름에다 1을 더한 크기보다 작다. 어떤 시스템에서는, 최대 가능한 호스트 이름의 길이가 심볼 MAXHOSTNAMELEN 어로 정해져있다. 그것은 `sys/param. h'에 정의되어 있다. 그것에 맞지 않는다면 실패로 처리하고 다시 시도하라.

gethostname은 만일 호스트 이름의 길이가 길어서 짤린다고 하더라도 호스트이름을 name에 저장하고 그것을 가리키는 포인터를 반환한다. 어떤 때는 잘려진 호스트이름으로도 충분할 때가 있다. 만일 그렇다면, 그때 발생한 에러코드는 무시할 수 있다.

함수 : int sethostname (const char *name, size_t length)

sethostname 함수는 length의 길이를 가진 문자열 name으로 호스트 기계의 이름을 설정한다. 오직 특권을 가진 프로세스만이 이 일을 하도록 허락된다. 보통 시스템이 부팅할때 딱 한 번 발생한다. 성공하면 반환값은 0이고 실패하면 -1이다.
 
다음의 errno는 이 함수를 위해 정의된 에러상황이다.
EPERM : 이 프로세스는 특권이 없기 때문에 호스트이름을 설정할 수 없다.

함수 : long int gethostid (void)

이 함수는 프로그램이 실행되고 있는 기계의 "호스트 ID"를 반환한다. 관례적으로, 반환되는 호스트 ID는 보통 기계의 주요한(primary) 인터넷 주소로써, long int로 변환된 것이다. 그러나 어떤 기계에서는 각각의 기계에 불변인(hard-coded) 단일한 번호로 의미가 없다.

함수 : int sethostid (long int id)

sethostid 함수는 id로 호스트 기계의 "호스트 ID"를 설정한다. 오직 특권을 가진 프로세스만이 이러한 일을 하도록 허용된다. 보통 그것은 시스템이 부팅될 때, 딱 한 번 발생한다.
 
성공하면 반환값은 0이고 실패하면 -1이다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EPERM

이 프로세스는 특권이 없기 때문에 호스트 이름을 설정할 수 없다.

ENOSYS

운영체제가 호스트 ID를 설정하는 것을 지원하지 않는다. 어떤 시스템에서, 호스트 ID는 불변의 단일한 숫자이기 때문에 의미가 없다.


26. 2 하드웨어/소프트웨어 타입 확인하기

당신은 당신의 프로그램이 실행되고 있는 컴퓨터의 타입에 대한 정보를 얻기 위하여 uname 함수를 사용할 수 있다. 이 함수와 그것과 연관된 데이터타입은 헤더파일 `sys/utsname. h'에 선언되어 있다.

데이터타입 : struct utsname

utsname 구조체는 uname 함수에의해 반환된 정보를 저장하는데 사용된다. 다음과 같은 멤버들을 갖는다.

char sysname[] : 사용중인 운영체제의 이름이다.

char nodename[]

이것은 특정한 컴퓨터의 네트웍 이름이다. GNU라이브러리에서, 이 값은 gethostname에 의해 반환된 것과 같은 값이다; 26. 1절 [Host Identification] 참조.

char release[] : 이것은 운영체제의 현재 개정판 번호이다.

char version[] : 이것은 운영체제의 현재 버전(version) 이다.

char machine[]

이것은 사용중인 하드웨어 타입에 대한 명세이다. 어떤 시스템은 이 정보를 직접적으로 커널로부터 얻을 수 있는 메커니즘을 제공한다. 이러한 메커니즘이 없는 시스템에서, GNU C 라이브러리는 그 라이브러리가 만들어지고 인스톨 될 때 정해진 형태에 기초하여 이 필드를 채운다. GNU 는 시스템 구성을 설명하기 위해서 세-부분을 사용한다; 세부분은 중앙처리장치, 제조회사, 그리고 시스템-타입이고, 그들은 대쉬로 구분된다. 어떤 경우에 그 세부분의 조합된 정보는 유용하지만, 대부분은 실제적으로 유용하지 않고 필요가 없다.
 
기계에서 그 값이 단지 하드웨어에 대한 명세만을 지원한다면, 그것은 두 개의 부분으로 형태의 이름이 구성된다.
예를 들면 다음과 같다:
"sparc-sun", "i386-anything ", "m68k-hp", "m68k-sony", "m68k-sun", "mips-dec"

함수 : int uname (struct utsname *info)

uname 함수는 info가 가리키고 있는 구조체를 운영체제와 호스트 기계에 대한 정보로 채운다. 음이 아닌 값은 성공적으로 정보가 저장되었음을 얘기하고 -1은 실패할 경우 반환된다. 오직 가능한 에러는 EFAULT 밖에 없는데, 그 에러는 항상 발생가능성이 있는 것으로써 간단히 얘기할 수는 없다.


목차 이전 : 25. 사용자와 그룹 다음 : 27. 시스템 구성 파라미터

'Programming > Unix Programing' 카테고리의 다른 글

부록 A : Language Features  (0) 2007.12.22
27. System Configuration  (0) 2007.12.22
26. System Information  (0) 2007.12.22
25. 사용자와 그룹  (0) 2007.12.22
24. Job Control(작업 제어)  (0) 2007.12.22
23. Processes  (0) 2007.12.22
Comment 0 Trackback 0
Top

25. 사용자와 그룹

목차 이전 : 24. 작업 제어 다음 26. 시스템 정보


25 사용자와 그룹

시스템에서 로그인하고 있는 모든 사용자는 사용자 ID(user ID)라고 불리는 한 개의 단일한 숫자에 의해서 구분된다. 각 프로세스는 그 프로세스를 가진 사용자의 억세스 권한을 알리는 유효 사용자 ID를 갖는다. 사용자들은 억세스 제어를 목적으로 하여 그룹으로 분류된다. 각 프로세스는 그 프로세스가 파일들을 억세스하기 위해서 사용할 수 있는 어떤 그룹들이 있는지를 알리는 한 개 이상의 그룹 ID(group ID)가 있다. 프로세스의 유효 사용자 ID 와 유효 그룹 ID들은 persona의 집합적인 형식이다. 이것은 그 프로세스가 어떤 파일을 억세스 할 수 있는지를 결정한다. 일반적으로, 프로세스는 부모 프로세스로 부터 persona를 상속받지만, 특별한 상황하에서는 그 persona를 변경해서 억세스 권한을 변경할 수 있다.

시스템에 있는 각 파일은 또한 사용자 ID 와 그룹 ID를 갖는다. 억세스제어(access control)는, 실행되고 있는 프로세스의 사용자 ID 와 그룹 ID를 파일의 사용자 ID 와 그룹 ID를 비교함으로써 수행된다. 그 시스템은 등록된 사용자들을 모두 데이터베이스에 저장하고, 다른 데이터베이스에 정의된 모든 그룹들을 저장한다. 그 데이터베이스를 시험하기 위해 사용할 수 있는 라이브러리 함수들이 있다.


25. 1 사용자와 그룹 ID들

컴퓨터 시스템에 등록된 각 사용자들은 사용자 이름(user name) 과 사용자ID(user ID) 에 의해 구분된다. 일반적으로, 각 사용자 이름은 한 개의 단일한 사용자 ID를 갖지만, 같은 사용자 ID에 여러개의 로그인 이름들을 가질 수 있다. 사용자 이름과 그에 해당하는 사용자 ID는 25. 12절 [User Database] 에 설명된, 당신이 억세스 할 수 있는 데이터베이스에 저장되어 있다.

사용자는 그룹 안에 분류된다. 각 사용자 이름은 디폴트로는 한 개의 그룹이지만, 한 개 이상의 그룹에 소속된다. 같은 그룹의 멤버인 사용자들은 그 그룹의 멤버가 아닌 사용자가 억세스할 수 없는 자원들(파일과 같은)을 공유한다. 각 그룹은 한 개의 그룹 이름과 그룹 ID를 갖는다. 25. 13절[Group Database] 에, 그룹 ID 또는 그룹 이름에 대한 정보를 어떻게 찾는지 나와있다.


25. 2 프로세스의 persona

어느 시간에, 각 프로세스는 프로세스의 권한을 결정하는 한 개가 단일한 사용자 ID와 한 개의 그룹 ID를 갖는다. 그들은 프로세스의 집합적인 persona라고 불리는데, 왜냐하면 그들은 억세스 제어의 목적을 위하여 "그것은 누구이다"를 결정하기 때문이다. 그 ID들은 프로세스의 유효 사용자 ID(effective user ID) 와 유효 그룹 ID(effective group ID) 라고 불린다. 당신의 로그인 쉘은 당신의 사용자 ID 와 당신의 디폴트 그룹 ID로 구성되는 persona로 시작한다. 일반적인 환경에서, 모든 당신의 다른 프로세스들은 그들의 값들을 상속받는다.

프로세스는 그 프로세스를 만들었던 사용자를 확인할 수 있는 실제사용자 ID(real user ID)와 사용자의 디폴트 그룹을 확인하는 실제 그룹 ID(real group ID)를 갖는다. 그들의 값들은 억세스 제어의 역할을 하지 않기 때문에, 우리는 그들을 persona의 부분으로 간주하지 않는다. 하지만 그들은 중요하다. 실제 와 유효 사용자 ID 둘은 모두 프로세스의 생존기간 동안에 변경될 수 있다. 25. 3절 [Why Change Persona] 참조. 부가적으로, 사용자는 여러 개의 그룹에 포함될 수 있기 때문에, persona에는 억세스 권한에 기여하는 부가적인 그룹 ID들이 포함된다. 어떻게 프로세스의 유효 사용자 ID와 그룹 ID들이 파일을 접근하는 권한에 영향을 미치는지에 대한 상세한 정보들은, 9. 8. 6절 [Access Permission] 참조.

프로세스의 사용자 ID는 kill 함수를 사용해서 시그널들을 보낼 수 있는 권한 또한 제어한다. 21. 6. 2절 [Signaling Another Process] 참조.


25. 3 왜 프로세스의 persona를 변경하는가

프로세스를 위하여 사용자 그리고/또는 그룹 ID들을 변경할 필요가 있는 가장 명백한 상황이 있는 곳은 로그인 프로그램이다. 로그인이 실행을 시작할 때, 그 사용자 ID는 루트이다. 작업은 로그인하고 있는 사용자의 사용자 ID와 그룹 ID들을 가진 쉘을 시작하는 것이다. (이것을 완전히 수행하기 위해서, 로그인은 persona 뿐만 아니라 실제 사용자와 그룹 ID들을 설정해야만 한다. 하지만 이것은 특별한 경우이다. ) persona를 변경하는 더 일반적인 경우는 보통의 사용자 프로그램에서 실제로 그 프로그램을 실행시키고 있는사용자에게는 억세스가 가능하지 않는 자원을 사용자가 억세스할 필요가 있을 때이다.

예를 들어, 당신이 당신의 프로그램에 의해 제어되지만 다른 사용자에 의해서 직접적으로 읽거나 갱신될 수 없는 어떤 파일이 있다고 했을 때, 다른 사용자에게는 개방되지 않은 어떤 종류의 프로토콜의 기능을 원하거나, 또는 그것이 가지고 있는 정보의 비밀이나 완전을 보장하기 원할 때, persona를 변경하는 것이 필요하다. 이 제한된 억세스의 종류는 원하는 자원과 매치되도록 유효 사용자와 그룹 ID를 프로그램이 변경함으로써 가능하다.

그러면, 파일안에 점수를 저장하는 게임 프로그램을 상상해보자. 게임프로그램 자체는 누가 그것을 실행시키는지에 상관없이 이 파일을 갱신할 수 있도록 할 필요가 있지만, 만일 사용자가 그 게임을 실행시키지 않고도 그 점수파일에 기록이 가능하다면, 그들은 그들이 좋아하는 어느 점수로 그 점수파일에 그들 점수를 기록할 수 있다. 어떤 사람은 이것을 상상할 수도 없거나, 아니면 비난할 것이다. 이러한 것은 점수 파일을 소유하는 새로운 사용자 ID와 로그인 이름을 만들고 이 사용자에 의해서만 오직 기록 가능하도록 그 파일을 만들면 방지할 수 있다. 그러면, 게임 프로그램이 이 파일을 갱신하기 원할 때, 그것은 그 게임의 유효 사용자 ID를 변경할 수 있다. 실제로, 프로그램은 점수파일에 기록할 수 있는 게임의 persona를 받아 들여야만 한다.


25. 4 어떻게 응용프로그램이 persona를 변경할 수 있는가

프로세스의 persoan을 변경하기 위한 능력은 무심한 비밀의 침해나, 또는 고의적인 남용의 소스(source)가 될 수 있다. persona를 변경하는, 프로그램에 포함된 문제는 특별한 상황들에 제한이 있기 때문이다.

당신은 제멋대로 당신이 원한다고 당신의 사용자 ID 나 그룹 ID를 변경 할 수는 없다; 오직 특권이 부여된 프로세스들만이 그것을 할 수 있다. 대신에, 프로그램을 위하여 persona를 변경하기 위한 일반적인 방법은 미리 특별한 사용자나 그룹으로 변경하여 프로그램이 시작되는 것이다. 이것은 파일의 억세스 모드의 setuid 와 setgid 비트들의 함수이다. 9. 8. 5절 [Permission Bits] 참조.

억세스 가능한 파일의 setuid 비트가 설정될 때, 그 파일은 자동적으로 그 파일을 소유한 사용자로 유효 사용자 ID를 변경하여 실행된다. 그와 같이, setgid 비트가 설정된 파일은 파일의 그룹으로 유효사용자 ID를 변경하여 실행된다. 23. 5절 [Executing a File] 참조. 특별한 사용자나 그룹 ID로 변경하여 만들어진 파일은 사용자나 그룹 ID에게 완전한 억세스를 보장한다. 파일 모드와 억세스가능성에 대한 일반적인 내용은 9. 8절 [File Attributes] 참조하라.

프로세스는 유효 사용자(또는 그룹) ID는 실제 ID로 언제든지 다시 변경할 수 있다. 프로그램을 좀더 견고한 프로그램으로 만들기 위해서, 그들이 필요치 않을 때는 그들에 대한 특별한 권한을 없애야(turn off) 한다.


25. 5 프로세스의 persona 읽기

다음은 실제와 유효 사용자와 그룹 ID들을 읽기 위한 함수들의 상세한 설명이다. 그 기능들을 사용하기 위해서는 당신은 헤더파일 `sys/types. h'와 `unistd. h'를 포함시켜야만 한다.

데이터타입 : uid__t

이것은 사용자 ID들을 나타내는데 사용되는 정수 데이터 타입이다. GNU 라이브러리에서, 이것은 unsigned int 와 같다.

데이터타입 : gid__t

이것은 그룹 ID들을 나타내기 위해서 사용되는 정수 데이터타입이다. GNU 라이브러리에서, 이것은 unsigned int 와 같다.

함수 : uid_t getuid (void)

getuid 함수는 프로세스의 실제 사용자 ID를 반환한다.

함수 : gid_t getgid (void)

getgid 함수는 프로세스의 실제 그룹 ID들을 반환한다.

함수 : uid_t geteuid (void)

geteuid 함수는 프로세스의 유효 사용자 ID를 반환한다.

함수 : gid_t getegid (void)

getegid 함수는 프로세스의 유효 사용자 ID 를 반환한다.

함수 : int getgroups (int count, gid_t *groups)

getgroups 함수는 프로세스의 부가적인 그룹 ID들에 대해서 묻는데 사용된다. 그들 그룹 ID들의 개수까지도 배열 groups에 저장된다; 함수로부터의 반환값은 실제로 저장된 그룹 ID들의 개수이다. 만일 count가 부가적인 그룹 ID들의 전체 개수보다 작다면, getgroups는 -1의 값을 반환하고 errno를 EINVAL로 설정한다. 만일 count 가 0이면, getgroups는 단지 부가적인(supplementary) 그룹 ID들의 전체 개수를 반환한다. 부가적인 그룹들을 지원하지 않는 시스템에서는, 이것은 항상 0이 될 것이다.
 
다음은 getgroups가 어떻게 모든 부가적인 그룹 ID들을 읽어서 사용하는지에 대한 것이다.
gid_t *read_all_groups (void)
{
int ngroups = getgroups (NULL, 0);
gid_t *groups = (gid_t *) xmalloc (ngroups * sizeof (gid_t));
int val = getgroups (ngroups, groups);
if (val < 0) {
free (groups);
return NULL;
}
return groups;
}


25. 6 사용자 ID 설정하기

이 절은 프로세스의 사용자 ID(실제 그리고/또는 유효)를 변경하기 위한 함수들을 설명하고 있다. 그 기능들을 사용하기 위해서, 당신은 헤더파일 `sys/types. h'와 `unistd. h'를 포함해야만 한다.

함수 : int setuid (uid_t newuid)

이 함수는 적당한 특권을 가지고 제공된 프로세스에서, 그 프로세스의 실제 사용자 ID 와 유효사용자 ID를 newuid로 설정한다. 만일 프로세스가 특권이 없다면, newuid는 실제 사용자 ID또는 저장된 사용자 ID(만일 시스템이 _POSIX_SAVED_IDS 를 지원한다면)중의 하나가 되어야만 한다. 이 경우, setuid는 오직 유효 사용자 ID만을 설정하고 실제 사용자 ID는 설정하지 않는다.
 
setuid 함수는 성공하면 0의 값을 반환하고 에러가 발생하면 -1의 값을 반환한다.  
다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EINVAL : newuid 인수의 값이 유용하지 않다.

EPERM

그 프로세스가 적당한 특권을 가지지 않는다; 당신은 정해진 ID로 변경하기 위한 권한을 가지지 않았다.

함수 : int setreuid (uid_t ruid, uid_t euid)

이 함수는 ruid로 프로세스의 실제 사용자 ID를 설정하고 euid로 유효 사용자 ID를 설정한다. 만일 ruid가 -1이면, 그것은 실제 사용자 ID를 변경하지 않음을 의미한다; 그처럼 만일 euid가 -1이면 유효사용자 ID를 변경하지 않음을 의미한다.
 
setreuid 함수는 저장된 ID(saved IDs)들을 지원하지 않는 4. 3 BSD 유닉스와의 호환성을 위해서 존재한다. 당신은 프로세스의 유효와 실제 사용자 ID들을 교환하기 위해서 이 함수를 사용할 수 있다. (특권을 가진 프로세스는 이 특별한 사용에 대해서 제한이 없다. ) 만일 저장된 ID들이 지원된다면, 당신은 이 함수대신에 25. 8절 [Enable/Disable Setuid] 에 있는 함수들을 사용하는 것이 좋다.
 
반환값은 성공하면 0이고 실패하면 -1이다.
다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EPERM

그 프로세스가 적당한 특권을 가지지 않았다; 당신은 정해진 ID로 변경하기 위한 권한이 없다.


25. 7 그룹 ID들을 설정하기

이 절은 프로세스의 그룹 ID들(실제 와 유효)을 변경하기 위한 함수들을 설명하고 있다. 이 기능들을 사용하기 위해서, 헤더파일 `sys/types. h'와 `unistd. h'를 포함해야만 한다.

함수 : int setgid (gid_t newgid)

이 함수는 적당한 특권을 가지고 제공된 프로세스의 실제와 유효 그룹 ID를 newgid로 설정한다. 만일 그 프로세스가 특권이 없다면, newgid는 반드시 실제 그룹 ID 또는 저장된 그룹 ID중의 하나와 동일해야만 한다. 이 경우, setgid는 오직 유효 그룹 ID만 설정하고 실제 그룹 ID는 설정하지 않는다. setgid를 위한 반환값과 에러 상황들은 setuid와 같다.

함수 : int setregid (gid_t rgid, fid_t egid)

이 함수는 rgid로 프로세스의 실제 그룹 ID 를 설정하고 egid로 유효 그룹 ID를 설정한다. 만일 rgid가 -1이면, 그것은 실제 그룹 ID를 변경하지 않음을 의미한다; 그와 같이 만일 egid가 -1이면, 유효 그룹 ID를 변경하지 않음을 의미한다.
 
setregid 함수는 저장된 ID들을 지원하지 않는, 4. 3 BSD 유닉스와의 호환성을 위해서 제공되었다. 당신은 프로세스의 유효와 실제 그룹 ID들을 교환하기 위해서 이 함수를 사용할 수 있다( 특권을 가진 프로세스들은 이 사용에 대해서 제한이 없다. ). 만일 저장된 ID들이 지원된다면, 이 함수를 사용하는 대신에 25. 8절 [Enable/Disable Setuid] 에 있는 함수들을 사용하라. setregid 를 위한 반환값과 에러상황들은 setreuid와 같다. GNU 시스템은 특권을 가진 프로세스가 그들의 부가적인 그룹 ID들을 변경하는 것을 허용한다. setgroups 나 initgroups를 사용하기 위해서, 당신은 헤더파일 `grp. h'를 포함해야만 한다.

함수 : int setgroups (size_t count, gid_t *groups)

이 함수는 프로세스의 부가적인 그룹 ID들을 설정한다. 그것은 오직 특권을 가진 프로세스로 부터 호출될 수 있다. count 인수는 배열 groups안에 있는 그룹 ID들의 개수를 지정한다.
 
이 함수는 성공하면 0을 반환하고 에러가 발생하면 -1을 반환한다. 다음의 errno 는 이 함수를 위해 정의된 에러상황이다.
EPERM : 호출한 프로세스가 특권이 없다.

함수 : int initgroups (const char *user, gid_t gid)

initgroups 함수는 사용자 이름 user를 위해서, 디폴트가 되도록 프로세스의 부가적인 그룹 ID들을 설정하기 위해서 setgroups를 호출한다. 그룹 ID gid 또한 포함된다.


25. 8 Setuid 억세스를 가능하게 하거나 불가능하게 하기

전형적인 setuid를 사용하는 프로그램에서 항상 특별한 억세스가 필요한 것은 아니다. 그래서 필요하지 않을 때 이 억세스를 꺼버리면, 무의식적인 억세스에 대한 빌미를 제공하지 않게된다. 만일 시스템이 저장된 사용자 ID 기능을 지원하지 않는다면, 당신은 setuid로 이것을 수행할 수 있다. 게임 프로그램이 시작할 때, 그것의 실제 사용자 ID는 jdoe이고, 유효 사용자 ID는 games이고, 그것의 저장된 사용자 ID 또한 games이다. 그 프로그램은 다음과 같이, 시작할 때 일단 사용자 ID값들을 기록하게 된다.

    user_user_id = getuid ();

    game_user_id = geteuid ();

그러면 setuid (user_user_id); 로 게임 파일 억세스를 끌 수 있고 setuid (game_user_id); 로 다시 켤 수 있다. 이 프로세스를 통해서, 실제 사용자 ID를 jdoe로 남고 저장된 사용자 ID는 games로 저장되기 때문에, 프로그램은 항상 다른 것으로 유효 사용자 ID를 설정할 수 있다.

저장된 사용자 ID 기능을 지원하지 않는 다른 시스템에서, 당신은 프로세스의 실제 사용자 ID 와 유효 사용자 ID를 교환하기 위해서는 setreuid를 사용해서 setuid 억세스를 켜고 끌 수 있다. 다음처럼;

    setreuid (geteuid (), getuid ());

이것은 실패하지 않고_항상 허용되는 특별한 경우이다.

왜 이것은 setuid 억세스를 토글링하는 효과를 갖는가? 게임 프로그램이 단지 시작되어 있다고 가정하고, 유효 사용자 ID가 games 일 동안 실제 사용자 ID는 jdoe라고 가정하자. 이 경우, 게임은 점수 파일을 기록할 수 있다. 만일 두 개의 유효 사용자 ID 와 실제 사용자 ID를 교환한다면, 실제 사용자 ID는 games가 되고 유효 사용자 ID는 jdoe가 된다; 이제 프로그램은 오직 jdoe만 억세스를 갖는다. 다른 교환은 유효 사용자 ID를 다시 games로 되돌리고 점수 파일에 억세스를 저장한다.

시스템의 두 종류를 다루기 위해서는, 다음처럼 프리프로세서를 사용해서 저장된 사용자 ID 기능을 지원하는지 테스트해 보아야 한다.

#ifdef _POSIX_SAVED_IDS
setuid (user_user_id);
#else
setreuid (geteuid (), getuid ());
#endif


25. 9 setuid 프로그램 예제

다음은 유효 사용자 ID를 변경하는 프로그램을 어떻게 준비하는지 보여주는 예제이다. 이것은 caber-toss라고 불리는 게임 프로그램의 일부분으로써 그 게임 프로그램은 오직 게임 프로그램 그 자체에 의해서만 점수 파일을 다룰 수 있도록 되어있다. 그 게임 프로그램은 실행파일이 set-user-ID 비트로 인스톨될 것이고 실행파일이 점수파일과 같은 사용자에 의해 소유된다고 가정한다. 전형적으로, 시스템 관리자는 이 목적을 위해서 게임과 같은 계정을 준비할 것이다.

실행가능파일은 모드 4755가 주어지고, 그래서 `ls -l'을 하면 다음과 같은 출력을 얻게된다.

-rwsr-xr-x 1 games 184422 Jul 30 15: 17 caber-toss

set-user-ID 비트는 파일 모드에서 `s'로 나타난다. 점수 파일을 모드 644로 주어지고, `ls -l'하면 다음과 같은 결과를 얻는다.

다음은 어떻게 변경된 사용자 ID를 준비할 것인지를 보여주는 프로그램의 일부분이다. 이 프로그램은 만일 시스템이 저장된 ID 기능을 지원한다면 그것을 사용하고, 그렇지 않다면 setreuid를 사용해서 유효 사용자 ID와 실제 사용자 ID를 교환한다.

#include <stdio. h>
#include <sys/types. h>
#include <unistd. h>
#include <stdlib. h>
 
/* 유효와 실제 사용자 ID들을 저장하라. */
 
static uid_t euid, ruid;
 
/* 원래의 값으로 유효 사용자 ID를 반환하라. */
 
void
do_setuid (void)
{
int status;
 
#ifdef _POSIX_SAVED_IDS
status = setuid (euid);
#else
status = setreuid (ruid, euid);
#endif
if (status < 0) {
fprintf (stderr, "Couldn't set uid. \n");
exit (status);
}
}
 
/* 실제 사용자 ID로 유효 사용자 ID를 설정하라. */
 
void
undo_setuid (void)
{
int status;
 
#ifdef _POSIX_SAVED_IDS
status = setuid (ruid);
#else
status = setreuid (euid, ruid);
#endif
if (status < 0) {
fprintf (stderr, "Couldn't set uid. \n");
exit (status);
}
}
 
/* 메인 프로그램 */
 
int
main (void)
{
/* 실제와 유효 사용자 ID들을 저장하라. */
ruid = getuid ();
euid = geteuid ();
undo_setuid ();
 
/* 게임을 하고 점수를 기록하라. */
. . .
}
 
메인 함수에서 첫째로 어떻게 실제 사용자 ID로 유효 사용자 ID를 되돌려 설정하는지에 대해서 주목하라. 이것은 사용자가 게임을 실행하고 있을 동안 수행되는 파일 억세스들로써 권한을 결정하기 위해서 실제 사용자 ID를 사용한다. 오직 프로그램이 점수 파일을 개방할 필요만 있을 때 다음처럼, 원래의 유효 사용자 ID로 되바꾼다.
 
/* 점수를 기록하라. */
int
record_score (int score)
{
FILE *stream;
char *myname;
 
/* 점수파일을 개방하라. */
do_setuid ();
stream = fopen (SCORES_FILE, "a");
undo_setuid ();
 
/* 파일에 그 점수를 기록하라. */
if (stream)
{
myname = cuserid (NULL);
if (score < 0)
fprintf(stream, "%10s: Couldn't lift the caber. \n", myname);
else
fprintf (stream, "%10s: %d feet. \n", myname, score);
fclose (stream);
return 0;
} else
return -1;
}


25. 10 setuid 프로그램을 만들기 위한 팁

setuid 프로그램은 원래 의도하지 않았던 사용자 억세스를 부여하는 오류를 범하기 쉽다_실제로, 만일 당신이 이러한 문제를 피하기를 원한다면, 각별한 주의를 해야만 한다. 다음은 의도되지 않은 억세스를 막고, 만일 그것일 발생했을 때 그 영향력을 최소화하기 위한 안내지침이다.

절대적인 필요가 없는 한 root 와 같은 특권을 가진 사용자 ID들은 setuid 프로그램을 가져서는 안된다. 만일 자원(resource)이 당신의 특정한 프로그램에게 특정한 것이라면, 특권을 가지지 않은 사용자 ID 또는 그룹 ID가 단지 그 자원을 다루기만 하도록, 새롭게 정의하는 것이 좋다.

유효 사용자 ID를 변경하는것과 함께 system 함수와 exec 함수를 사용하는 것에 대해서는 주의를 해야만 한다. 당신 프로그램의 사용자가 변경된 사용자 ID를 사용하여 프로그램을 실행시키는 것을 허가하지 말아라. execlp 와 execvp 함수들도 위험을 내포하고 있다 (그들은 사용자의 PATH 환경변수에 의존하여 실행되기 때문이다. ) 만일 변경된 ID로 다른 프로그램을 실행해야만 한다면, 실행가능 파일을 절대 파일 이름( 6. 2. 2절 [File Name Resolution] 참조. ) 어로 지정하고, 실행가능 파일과 원래의 users 와 같은 모든 포함된 디렉토리들이 다른 프로그램과 함께 그것의 위치를 변경하지 못하도록 보호를 해야만 한다.

실제로 자원을 사용하는 프로그램의 일부분에서 자원을 제어하고 있는 사용자 ID만을 사용하라. 당신이 그 작업을 마쳤을 때, 실제 사용자의 사용자 ID로 유효 사용자 ID를 반환하라. 25. 8절 [Enable/Disable Setuid] 참조.

만일 당신의 프로그램에서 setuid 부분이 제어할 수 있는 자원이외에 다른 파일을 억세스할 필요가 있다면, 그와같은 파일을 억세스할 수 있는 권한을 가진 실제 사용자를 조회해야만 한다. 당신은 그와같은 일을 위해서 access 함수 ( 9. 8. 6절 [Access Permission] 참조. )를 사용할 수 있다; 그것은 유효 ID들보다는 실제 사용자와 그룹 ID들을 사용한다.


25. 11 누가 로그인 했는지 확인하기

이 절에 설명된 함수들을 사용해서 프로세스를 실행시키고 있는 사용자의 이름과 현재 세션에 로그인 된 사용자의 이름을 알아낼 수 있다. getuid와 friends 함수( 25. 5절 [Reading Persona] 참조. )를 또한 참조하라. getlogin 함수는 `unistd. h'에 선언되어 있고 cuserid 와 L_cuserid는 `stdio. h'에 선언되어 있다.

함수 : char *getlogin (void)

getlogin 함수는 프로세스가 제어하고 있는 터미널에 로그인 되어 있는 사용자의 이름이 포함된 문자열을 가리키는 포인터를 반환하거나, 만일 그 정보를 결정할 수 없다면 널 포인터를 반환한다. 문자열은 정적으로 할당되고 이 함수나 cuserid 함수의 연속적인 호출에 의해서 덧씌워진다.

함수 : char * cuserid (char *string)

cuserid 함수는 프로세스의 유효 ID와 연관된 사용자 이름이 포함된 string을 가리키는 포인터를 반환한다. 만일 string이 널 포인터가 아니라면, 그 string은 적어도 L_cuserid개의 문자를 저장할 수 있는 배열이 된다; 사용자의 이름이 포함된 문자열을 이 string에 반환된다. 그렇지 않다면, 정적인 지역에 있는 문자열을 가리키는 포인터가 반환된다. 이 string는 정적으로 할당되고 이 함수나 getlogin 함수의 연속적인 호출에 의해서 덧씌워질 것이다.

매크로 : int L__cuserid

사용자의 이름을 저장하기에 필요한 배열이 얼마나 길어야 하는지를 지적하는 정수 상수이다. 그 함수들은 이 세션에 로그인 된 사용자나 프로세스를 실행시키고 있는 사용자를 명확하게 확인하도록 허용한다. (그들은 setuid 프로그램이 포함되었을 때는 다를 수 있다; 25. 2절 [Process Persona] 참조. ) 사용자는 그 함수들을 속일 수 있는 방법이 아무 것도 없다.

대부분의 경우에, 사용자를 찾아내는 환경변수 LOGNAME를 사용하는 것이 더욱 유용하다. 이것은 사용자가 제멋대로 LOGNAME 을 설정할 수 있기 때문에 정밀함에 있어서는 좀더 유연성이 있다. 22. 2. 2절 [Standard Environment] 참조.


25. 12 사용자 데이터베이스

이 절은 등록된 사용자의 데이터베이스를 검색하고 찾기 위한 모든 것에 대해서 설명한다. 데이터베이스 자체는 거의 대부분의 시스템에서 `/etc/passwd'에 보존되어 있지만, 어떤 시스템에서는 특별한 네트웍 서버에게 그것을 억세스할 수 있도록 한다.

 

25. 12. 1 사용자의 정보를 담고 있는 데이터 구조체

시스템 사용자 데이터베이스를 억세스 하기 위하여 함수와 데이터 구조체들은 헤더파일 `pwd. h'에 선언되어 있다.

데이터타입 : struct passwd

passwd 데이터구조체는 시스템 사용자 데이터베이스 안에 엔트리들에 대한 정보를 저장하는데 사용된다. 그것은 적어도 다음과 같은 멤버들을 갖고 있다.

char *pw_name : 사용자 로그인 이름.

char *pw_passwd : 암호화된 비밀번호(password) 문자열.

uid_t pw_uid : 사용자 ID 번호.

gid_t pw_gid : 사용자의 디폴트 그룹 ID 번호.

char *pw_gecos

이 문자열은 전형적으로 사용자의 실제 이름을 포함하고, 가능하면 전화번호와 같은 다른 정보도 포함된다.

char *pw_dir

사용자의 홈 디렉토리(home directory), 또는 초기작업디렉토리. 이것에 대한 해석이 시스템-의존적인 경우에는 널 포인터가 되어야한다.

char *pw_shell

사용자의 디폴트 쉘, 이나 사용자가 로그인할 때 실행되는 초기 프로그램. 시스템에서 디폴트로 지정된 것이 사용되는 경우에는, 널 포인터가 되어야한다.

 

25. 12. 2 한 명의 사용자에 대해 자세히 알아보기

특정한 사용자에 대한 정보를 알아내기 위해서는, getpwuid 또는 getpwnam을 사용해서 시스템 사용자 데이터베이스를 검색할 수 있다. 그 함수들은 헤더파일 `pwd. h'에 선언되어 있다.

함수 : struct passwd * getpwuid (uid_t uid)

이 함수는 사용자 ID가 uid인 사용자에 대한 정보를 담고 있는 정적으로 할당된 구조체를 가리키는 포인터를 반환한다. 이 구조체는 getpwuid의 연속적인 호출에 의해서 덧씌워진다. 널 포인터 값은 데이터베이스에 사용자 ID가 uid인 사용자가 없다는 말이다.

함수 : struct passwd *getpwnam (const char *name)

이 함수는 사용자 이름이 name인 사용자에 대한 정보를 담고 있는 정적으로 할당된 구조체를 가리키는 포인터를 반환한다. 이 구조체는 getpwnam의 연속적인 호출로 덧씌워질 것이다. 널 포인터 값은 사용자의 이름이 name인 사용자가 데이터베이스에 없다는 말이다.

 

25. 12. 3 모든 사용자 리스트를 검색하기

이 절은 한 번에 한 사용자씩, 시스템에 있는 모든 사용자들의 리스트를 읽을 수 있기 위해서는 프로그램에서 어떻게 해야하는지를 설명한다. 이곳에 설명된 함수는 헤더파일 `pwd. h'에 선언되어 있다. 당신은 특정한 파일로부터 사용자 엔트리들을 읽기 위해서 fgetpwent 함수를 사용할 수 있다.

함수 : struct passwd * fgetpwent (FILE *stream)

이 함수는 stream 어로부터 다음 사용자 엔트리를 읽고 그 엔트리를 가리키는 포인터를 반환한다. 그 구조체는 정적으로 할당되었고, fgetpwent의 연속적인 호출로 덧씌워진다. 그러므로, 만일 당신이 그 정보를 저장하길 원한다면 구조체의 내용을 다른 곳에 복사해 놓아야만 한다. stream은 표준 password 데이터베이스 파일과 같은 형식의 파일이어야만 한다. 이 함수는 시스템 V 로부터 왔다.

사용자 데이터베이스의 모든 엔트리들을 검색하는 방법에는 setpwent, getpwent, 그리고 endpwent 가 있다.

함수 : void setpwent (void)

이 함수는 getpwent 가 사용자 데이터베이스를 읽기 위해서 사용하는 스트림을 초기화한다.

함수 : struct passwd * getpwent (void)

getpwent 함수는 setpwent 에 의해 초기화된 stream으로부터, 다음 엔트리를 읽고 엔트리를 가리키는 포인터를 반환한다. 그 구조체는 정적으로 할당되었고 getpwent의 연속적인 호출에 의해서 덧씌워진다. 그래서 만일 당신이 그 정보를 저장하기를 원한다면 구조체의 내용을 다른 곳으로 복사해놓아야만 한다.

함수 : void endpwent (void)

이 함수는 getpwent에 의해 사용된 내부 스트림을 닫는다.

 

25. 12. 4 사용자 엔트리를 기록하기

함수 : int putpwent (const struct passwd *p, FILE *stream)

이 함수는 표준 사용자 데이터베이스 파일에서 사용된 형식으로, 스트림 stream에 사용자 엔트리 *p를 기록한다. 성공하면 반환값은 0이고 실패하면 0이 아닌 값이다. SVID 와의 호환성을 위해서 이 함수는 존재한다. 우리는 구조체 struct passwd 가 위에 설명된 표준적인 것을 제외하고는 부가적으로 아무런 멤버들을 가지지 않았다고 예상이 되는 경우에만 이 함수를 사용하고, 그렇지 않은 경우들에는 이 함수를 사용하지 말 것은 권장한다; 전형적인 유닉스 데이터베이스와 더불어 사용자에 대한 다른 확장된 정보를 합병하는 시스템에서, 이 함수를 사용해서 엔트리를 더하는 것은 중요한 정보를 생략하게 되는 것이다.

함수 putpwent 는 `pwd. h'에 선언되어 있다.


25. 13 그룹 데이터 베이스

이 절은 등록된 그룹들의 데이터베이스를 어떻게 검색하고 찾을 것인지에 대한 모든 것이 설명되어 있다. 대부분의 시스템에서 데이터베이스 그 자체는 `/etc/group'에 보존되고 있는데, 어떤 시스템에서 특정한 네트웍 서비스는 그것에 대한 억세스를 제공한다.

 

25. 13. 1 그룹을 위한 데이터 구조체

시스템 그룹 데이터베이스를 억세스하기 위한 함수와 데이터구조체는 헤더파일 `grp. h'에 선언되어 있다.

데이터 타입 : struct group

group 구조체는 시스템 그룹 데이터베이스 안에 엔트리에 대한 정보를 저장하기 위해서 사용된다. 그 구조체는 적어도 다음과 같은 멤버들을 갖고 있다.

char *gr_name 그룹의 이름.

gid_t gr_gid 그룹의 그룹 ID

char **gr_mem

그룹에 있는 사용자들의 이름을 가리키는 포인터의 벡터. 각 사용자 이름은 널문자로 끝나는 문자열이고, 벡터 자체는 끝이 널 포인터로 끝난다.

 

25. 13. 2 한 개의 그룹에 대한 상세한 정보를 알아내기

getgrgid 나 getgrnam 을 사용해서 정해진 그룹에 대한 정보를 그룹 데이터베이스에서 찾아낼 수 있다. 그 함수들은 `grp. h'에 선언되어 있다.

함수 : struct group * getgrgid (gid_t gid)

이 함수는 그룹 ID 로 gid를 갖고 있는 그룹에 대한 정보를 갖고 있는 정적으로 할당된 구조체를 가리키는 포인터를 반환한다. 이 구조체는 getgrgid 의 연속적인 호출에 의해서 덧씌워질 것이다. 널 포인터는 그룹 ID로 gid 를 가진 그룹이 없다는 말이다.

함수 : struct group * getgrnam (const char *name)

이 함수는 그룹 이름으로 name을 가진 그룹에 대한 정보를 담고 있는 정적으로 할당된 구조체에 대한 포인터를 반환한다. 이 구조체는 getgrnam의 연속적인 호출에 의해서 덧씌워진다. 널 포인터는 그룹이름으로 name을 가진 그룹이 없다는 말이다.

 

25. 13. 3 모든 그룹에 대한 리스트를 검색하기

이 절은 프로그램에서 한 번에 한 그룹씩, 시스템에 있는 모든 그룹에 대한 리스트를 어떻게 읽을 수 있는지를 설명한다. 이곳에 설명된 함수들은 헤더파일 `grp. h'에 설명되어 있다. 특정한 파일로부터 그룹 엔트리들을 읽기 위해서 fgetgrent 함수를 사용할 수 있다.

함수 : struct group * fgetgrent (FILE *stream)

fgetgrent 함수는 stream으로부터 다음 엔트리를 읽고, 그 엔트리에 대한 포인터를 반환한다. 그 구조체는 정적으로 할당되었고 fgetgrent 의 연속적인 호출에 의해 덧씌워지기 때문에 만일 당신이 그 정보를 저장하기를 원한다면 그 구조체의 내용을 다른 곳으로 복사해놓아야만 한다. stream은 표준 그룹 데이터파일과 같은 형식을 가진 파일이어야만 한다.

그룹 데이터베이스에 있는 모든 엔트리들을 검색하는 방법은 setgrent, getgrent, 그리고 endgrent 가 있다.

함수 : void setgrent (void)

이 함수는 그룹 데이터베이스로부터 읽기 위한 스트림을 초기화한다. getgrent 함수의 호출에서 이 스트림이 사용된다.

함수 : struct group * getgrent (void)

getgrent 함수는 setgrent 에 의해 초기화된 스트림으로부터 다음 엔트리를 읽고 그 엔트리에 대한 포인터를 반환한다. 그 구조체는 정적으로 할당되었고 getgrent의 연속적인 호출에 의해서 덧씌워진다. 그래서 만일 당신이 그 정보를 저장하기를 원한다면 그 구조체의 내용을 다른 곳으로 복사해놓아야만 한다.

함수 : void entgrent (void)

이 함수는 getgrent 에 의해 사용된 내부스트림을 닫는다.


25. 14 사용자와 그룹 데이터베이스에 대한 예제

다음은 시스템 데이터베이스 질의 함수들을 사용하는 예를 보여주는 예제 프로그램이다. 다음 프로그램은 프로그램을 실행시키고 있는 사용자에 대한 어떤 정보를 출력한다.

#include <grp. h>
#include <pwd. h>
#include <sys/types. h>
#include <unistd. h>
#include <stdlib. h>
 
int
main (void)
{
uid_t me;
struct passwd *my_passwd;
struct group *my_group;
char **members;
 
/* 사용자 ID에 대한 정보를 얻어라. */
me = getuid ();
my_passwd = getpwuid (me);
if (!my_passwd) {
printf ("Couldn't find out about user %d. \n", (int) me);
exit (EXIT_FAILURE);
}
 
/* 정보를 출력하라. */
printf ("I am %s. \n", my_passwd->pw_gecos);
printf ("My login name is %s. \n", my_passwd->pw_name);
printf ("My uid is %d. \n", (int) (my_passwd->pw_uid));
printf ("My home directory is %s. \n", my_passwd->pw_dir);
printf ("My default shell is %s. \n", my_passwd->pw_shell);
 
/* 디폴트 그룹 ID에 대한 정보를 얻어라. */
my_group = getgrgid (my_passwd->pw_gid);
if (!my_group) {
printf ("Couldn't find out about group %d. \n",
(int) my_passwd->pw_gid);
exit (EXIT_FAILURE);
}
 
/* 정보를 출력하라. */
printf ("My default group is %s (%d). \n",
my_group->gr_name, (int) (my_passwd->pw_gid));
printf ("The members of this group are: \n");
members = my_group->gr_mem;
while (*members)
{
printf (" %s\n", *(members));
members++;
}
 
return EXIT_SUCCESS;
}
 
다음은 이 프로그램을 통한 출력이다.
I am Throckmorton Snurd.
My login name is snurd.
My uid is 31093.
My home directory is /home/fsg/snurd.
My default shell is /bin/sh.
My default group is guest (12).
The members of this group are:
friedman
tami


목차 이전 : 24. 작업 제어 다음 26. 시스템 정보

'Programming > Unix Programing' 카테고리의 다른 글

27. System Configuration  (0) 2007.12.22
26. System Information  (0) 2007.12.22
25. 사용자와 그룹  (0) 2007.12.22
24. Job Control(작업 제어)  (0) 2007.12.22
23. Processes  (0) 2007.12.22
22. 프로세스의 시동과 종료(Process StartUp)  (0) 2007.12.22
Comment 0 Trackback 0
Top

24. Job Control(작업 제어)

목차 이전 : 23. 프로세스 다음 : 25. 사용자와 그룹


24 작업 제어

작업제어는 단일한 로그인 세션에서 사용자가 여러개의 프로세스 그룹들(또는 작업들) 사이에서 제어를 옮기는 것을 허용하는 프로토콜을 적용한다. 작업제어 기능은 대부분의 프로그램에서 자동적으로 적당하게 수행되고, 작업제어에 대한 특별한 어떤 것을 할 필요가 없다. 그래서 당신이 쉘이나 로그인 프로그램을 만들지 않는다면 이 장에 있는 것들을 무시할 수 있다.

당신이 이 장에 나와있는 것들을 이해하기 위해서는 시그널 핸들링(21장 [Signal Handling] 참조)과 프로세스 생성(23.2절 [Process Creation Concepts] 참조) 과 연관된 개념에 친숙해질 필요가 있다.


24.1 작업제어의 개념

대화형(interactive) 쉘의 기본적인 목적은 사용자 터미널로부터 명령문을 읽고 그 명령문에의해 지정된 프로그램을 실행하도록 프로세스들을 만드는 것이다. 그것은 fork( 23.4절 [Creadting a Process] 참조.)와 exec ( 23.5절 [Executing a File] 참조.)함수를 사용해서 할 수 있다.

단일한 명령문은 단지 한 개의 프로세스를 실행시키지만_종종 한 개의 명령문도 여러개의 프로세스들을 사용한다. 만일 당신이 쉘 커맨드에서 `|'을 사용한다면 당신은 명시적으로 그들 프로세스안에 여러개의 프로그램들을 요청한것이다. 그러나 만일 당신이 한 개의 프로그램을 실행시킬지라도, 그것이 내부적으로는 여러개의 프로세스들을 사용할 수 있다. 예를들어, `cc -c foo.c'와 같은 단일한 컴파일 명령문은, 전형적으로 네 개의 프로세스들을 사용한다. 만일 당신이 make를 실행한다면, 그 작업은 분리된 프로세스 안에서 다른 프로그램을 실행하는 것이다.

단일한 명령문에 소속되어 있는 프로세스들은 프로세스 그룹이나 작업(job)이라고 불린다. 당신은 동시에 그들 모든 프로세스들을 작동시킬 수 있다. 예를들어, C-c를 타이핑하면 전면 프로세스 그룹에 있는 모든 프로세스가 종료되도록 시그널 SIGINT를 보낸다. 세션은 프로세스들의 좀 큰 그룹이라고 할 수 있다. 일반적으로 단일한 로그인에 있는 모든 프로세스들은 같은 세션에 속한다.

모든 프로세스는 프로세스 그룹에 속한다. 어떤 프로세스가 만들어질 때, 그것은 부모 프로세스와 같은 프로세스 그룹과 세션의 멤버가 된다. 당신은 setpid 함수를 사용해서 다른 프로세스 그룹으로 소속을 바꿀 수 있는데, 그때 그 프로세스 그룹은 같은 세션에 소속된 프로세스 그룹이다.

다른 세션에 프로세스를 소속시키는 유일한 방법은 setsid 함수를 사용해서, 새로운 세션이나, 또는 세션 리더의 처음 프로세스를 만드는 것이다. 새로운 프로세스 그룹에 세션리더를 소속시키면, 당신은 다시 프로세스 그룹의 외부로 그것을 다시 옮길 수 없다.

보통, 새로운 세션들은 시스템 로그인 프로그램에 의해 만들어지고 세션 리더는 사용자의 로그인 쉘을 실행시키는 프로세스이다. 작업제어를 지원하는 쉘은 어떤 시간에 어떤 작업이 터미널을 사용할 수 있도록 제어를 조정해야만 한다. 그렇지않다면 여러개의 작업들이 동시에 터미널로부터 입력을 받아들이려 시도할것이고, 어떤 프로세스가 사용자가 타입한 입력을 받을 것인지가 혼란하게 될 수도 있다. 이것을 방지하기 위해서, 쉘은 이 장에 설명된 프로토콜을 사용해서 터미널 드라이버와 협력해야만 한다.

쉘은 한 번에 오직 한 프로세스 그룹에게만 터미널을 제어하는 제한없는 권한을 줄 수 있다. 이처럼 터미널을 제어하고 있는 프로세스 그룹을 전면 작업(foreground job)이라고 부른다. 터미널을 억세스 하지 않고 실행중인, 쉘에 의해 처리되고 있는 다른 프로세스 그룹들은 배경작업(background job)이라고 불린다.

만일 배경 작업이 터미널을 제어하여 터미널로부터 읽거나 쓰거나 할 필요가 있다면, 그것은 터미널 드라이버에 의해 멈추어진다. 사용자는 SUSP문자( 12.4.9절 [Special Characters] 참조)를 입력함으로써 전면 작업을 멈출 수 있고 프로그램은 SIGSTOP시그널을 보냄으로써 어떤 작업을 멈출 수 있다. 작업들이 멈추었을 때 쉘은 그들에 대하여 사용자에게 알릴 책임이 있고, 멈추어진 작업에 대해서 계속 진행할 것인지에 대한 여부를 사용자에게 선택하도록 허용하기 위한 메카니즘의 제공과 전면 작업과 배경 작업사이의 작업전환도 쉘에게 책임이 있다. 제어중인 터미널에 입/출력 하는것에 대한 자세한 정보는 24.4절 [Access to the Terminal] 참조.


24.2 작업제어는 선택적이다

모든 운영체제가 작업제어를 지원하는 것은 아니다. GNU 시스템은 작업제어를 지원하지만, 만일 다른 시스템에서 GNU 라이브러리를 사용한다고해도 그 시스템에서 작업제어를 지원하지 않을 수 도 있다. 당신은 시스템이 작업제어를 지원하는지에 대한 여부를 시험하기 위해서 컴파일할 때 _POSIX_JOB_CONTROL 매크로를 사용할 수 있다. 27.2절[System Options] 참조.

만일 작업제어가 지원되지 않으면, 한 세션만다 오직 한 개의 프로세스 그룹만 존재하고 그 프로세스 그룹은 항상 전면 작업으로써 행동한다. 부가적인 프로세스 그룹을 만드는 함수에는 에러코드 ENOSYS를 사용해서 간단히 실패했음을 알린다.

다양한 작업제어 시그널(21.2.5절 [Job Control Signals] 참조.)들을 대표하고 있는 매크로들은 작업제어가 지원되지 않는다고 할지라도 정의되어 있다. 그렇지만, 작업제어를 지원하지 않는 시스템에서 그 시그널들을 결코 발생되지 않고, 에러를 보고하거나 아무것도 하지않는 등의 시그널에 대한 행동을 정하거나 시험하거나 보내려고 시도하지 않는다.


24.3 프로세스의 터미널 제어하기

프로세스의 속성들중 하나는 그 프로세스가 제어중인 터미널이다. fork를 사용해서 만들어진 자식 프로세스는 그들의 부모 프로세스로부터 제어중인 터미널을 상속받는다. 이와같은 방법으로, 한 세션안에 있는 모든 프로세스들은 세션 리더(session leader)로부터 제어중인 터미널을 상속받는다. 터미널의 제어권을 가진 세션리더를 터미널의제어중인 프로세스(controlling process)라고 불린다.

당신이 로그인할 때 시스템이 당신을 위해서 그 일을 하기 때문에, 당신은 세션에 터미널 제어권을 할당하는 정확한 메카니즘에 대해서는 걱정할 필요가 없다. 개별적인 프로세스가 새로운 세션의 리더가 되기위해서 setsid를 호출할 때, 그 프로세스는 제어중인 터미널로부터 단절된다.


24.4 제어중인 터미널 억세스

터미널을 제어하고 있는 전면작업안의 프로세스들은 터미널에 대한 제한 없는 억세스 권한을 가진다; 배경프로세스들은 권한을 가지지 않는다. 이 절에서는 배경작업에 있는 프로세스가 터미널을 제어하려 시도할 때 어떤 일이 발생하는지에 대해서 자세히 설명하고 있다.

배경작업에 있는 프로세스가 전면작업이 제어하고 있는 터미널로부터 읽으려 시도할 때, 프로세스 그룹은 SIGTTIN 시그널을 받는다. 이것은 그룹안에 있는 모든 프로세스들의 동작을 멈추게 하는 원인이 된다(그 시그널이 처리되지 않거나 그들 스스로 멈추게 하지 않는다면). 그렇지만 읽기를 시도한 프로세스가 이 시그널을 무시하거나 블록한다면, 대신에 EIO 에러를 얻게 될 것이다.

유사하게, 배경작업에 있는 프로세스가 전면작업에서 제어하고 있는 터미널에 출력을 시도한다면, 그것에 대응된 디폴트 동작은 프로세스 그룹에게 SIGTTOU 시그널을 보내는 것이다. 그렇지만, 그 동작은 지역 모드 플래그(local modes flags)(12.4.7절 [Local Modes] 참조.)의 TOSTOP 비트에 의해서 갱신된다. 만일 이 비트가 설정되지 않는다면(이것이 디폴트이다.), 전면 작업에 의해 제어중인 터미널에 배경작업이 출력을 시도하는 것에는 시그널을 보내지 않고 항상 허가된다. 또한 출력을 시도하는 프로세스에 의해 SIGTTOU 시그널이 무시되거나 블록된다면 이때도 출력은 허가된다.

프로그램에서 할 수 있는 터미널 명령들의 대부분은 입력이나 출력을 위한 것이다. 기본적인 입력과 출력 함수에 대한 정보는 8.2절 [I/O Primitives] 참조.


24.5 고아가된 프로세스 그룹들

터미널 제어중이던 프로세스가 종료되면, 그 터미널은 해제되고 새로운 세션에 의해서 다시 제어될 수 있다.(실제로, 다른 사용자가 터미널 상에서 로그인 할 수 있다.) 이것은 만일 예전의 세션에 있는 어떤 프로세스가 그 터미널을 계속 사용하려고 시도한다면 문제가 발생할 수 있다.

그러한 문제들을 막기위해서, 세션리더가 종료된 후에도 계속 실행되고 있는 프로세스 그룹들은 고아가된 프로세스 그룹으로써 표시된다. 고아 프로세스 그룹(orphand process group) 안에 소속된 프로세스들은 터미널에 읽거나 쓰기를 할 수 없다. 만일 그렇게 하려 시도한다면 EIO 에러를 얻게 될 것이다.

어떤 프로세스 그룹이 고아가 되었을 때, 그 프로세스들은 SIGHUP 시그널을 받는다. 보통, 이것은 프로세스를 종료시키는 원인이 된다. 그렇지만, 만일 프로그램이 이 시그널을 무시하거나 시그널을 위한 핸들러를 만들었다면(21장 [Signal Handling] 참조.), 터미널을 제어중이던 프로세스가 종료되었다고 하더라도 실행중이던 고아 프로세스들은 계속 그 작업을 수행할 수 있다; 그렇지만 더 이상은 터미널의 억세스를 시도할 수 없다.


24.6 작업제어 쉘 실행시키기

이 절은 쉘이 작업 제어를 수행하기 위해서는 무엇을 해야만 하는지, 그 개념이 포함된 간단한 예제 프로그램을 통해서 설명하고 있다.

24.6.1절 [Data Structures]

이곳에서는 그 예제를 소개하고, 주요한 데이터 구조체를 소개하고 있다.

24.6.2절 [Initializing the Shell]

쉘이 작업제어를 준비하기 위해서 수행해야만 하는 동작들에 대해서 논의하고 있다.

24.6.3절 [Launching Jobs]

명령문을 실행하기 위해서 어떻게 작업들을 만들것이지에 대한 정보가 포함되어 있다.

24.6.4절 [Foreground and Background]

배경작업에 대립하는것으로써 전면으로 작업을 진출시킬 때 쉘이 무엇을 다르게 해야 만하는지를 설명하고 있다.

24.6.5절 [Stoppend and Terminated Jobs]

쉘에게 작업상황을 보고하기에 대해서 설명한다.

24.6.6 [Continuing Stopped Jobs]

작업이 멈추어졌을 때 다시 계속하기 위해서 어떻게 해야하는지를 설명한다.

24.6.7 [Missing Pieces] : 쉘의 다른 부분들에 대해서 설명하고 있다.

 

24.6.1 쉘을 위한 데이터 구조체들

이 장에 포함된 모든 예제 프로그램들은 간단한 쉘 프로그램의 일부분이다. 이 절은 이 예제에서 사용되고 있는 데이터 구조체와 유틸리티 함수들을 설명하고 있다.

간단한 쉘은 주로 두 개의 데이터 구조체를 다룬다. job 타입(type)은 파이프(pipes)로 서로 연결된 서브프로세스들의 집합인 작업에 대한 정보를 포함한다. process 타입(type)은 단일한 서브프로세스에 대한 정보를 저장하고 있다. 다음은 적절한 데이터 구조체 선언들이다.

/* process는 한 개의 단일한 프로세스이다. */
typedef struct process
{
struct process *next; /* 파이프라인에 있는 다음 프로세스 */
char **argv; /* exec을 위한 */
pid_tpid; /* 프로세스 ID */
char completed; /* 프로세스가 수행되었으면 참이다. */
char stopped; /* 만일 프로세스가 멈추었으면 참이다. */
int status; /* 보고된 상황들의 값 */
} process;
 
/* job 은 프로세스들의 파이프라인이다. */
typedef struct job
{
struct job *next; /* 다음 활동할 작업 */
char *command; /* 메시지를 위해서 사용되는, 커맨드 라인 */
process *first_process; /* 작업안에 있는 프로세스의 리스트 */
pid_t pgid; /* 프로세스 그룹 ID */
char notified; /* 사용자가 멈춘 작업에 대해서 말했으면 참이다. */
struct termios tmodes; /* 저장된 터미널 모드들 */
int stdin, stdout, stderr; /* 표준 입/출력 채널들 */
} job;
 
/* 활성화된 작업들은 리스트안에 연결되어 있고 이것이 그 헤드(head)이다.*/
job *first_job = NULL;
 
다음은 작업 오브젝트들은 운영하기 위해서 사용되는 유틸리티 함수들에 대한 것이다.
 
/* pgid를 통해서 활성 작업을 찾아라. */
job *
find_job (pid_t pgid)
{
job *j;
 
for (j = first_job; j; j = j->next) {
if (j->pgid == pgid)
return j;
}
return NULL;
}
 
/* 만일 job 안에 있는 모든 프로세스들이 완료되었거나 멈추어있다면 참을 리턴한다 */
int
job_is_stopped (job *j)
{
process *p;
 
for (p = j->first_process; p; p = p->next) {
if (!p->completed && !p->stopped)
return 0;
}
return 1;
}
 
/* 만일 job 안에 있는 모든 프로세스들이 완료되었다면 참을 리턴 */
int
job_is_completed (job *j)
{
process *p;
 
for (p = j->first_process; p; p = p->next) {
if (!p->completed)
return 0;
}
return 1;
}

 

24.6.2 쉘 초기화하기

일반적으로 작업제어를 수행하는 쉘 프로그램이 시작될 때, 그 쉘이 이미 자신의 작업제어를 수행하고 있는 다른 쉘로부터 호출되었을 경우에는 주의를 해야만 한다. 서브쉘(subshell)에서 작업제어가 가능하게 되기전에 우선 부모 쉘에 의해서 전면에 놓여져야만 한다. 그것은 getpgrp로 초기 프로세스 그룹 ID를 얻고, 현재 터미널을 제어하고 있는 전면작업의 프로세스 그룹 ID와 그것을 비교해서 한다( tcgetpgrp함수를 사용해서 추출될 수 있다. )

만일 그 서브쉘이 전면 작업으로써 실행되고 있지 않다면, 그것은 그 자신의 프로세스 그룹에 SIGTTIN 시그널을 보냄으로써 스스로 멈춰야만 한다. 그것은 전면에 그 자신을 제멋대로 놓을수 없다; 사용자가 부모 쉘에게 이것을 시키도록 기다려야만 한다. 만일 그 서브쉘이 계속된다면, 검사를 반복하고 만일 그것이 여전히 전면에 있지 않다면 다시 스스로 멈춘다.

일단 서브쉘이 부모 쉘에 의해서 전면에 놓이게되면, 서브쉘은 자신의 작업 제어가 가능하게 된다. 자신소유의 프로세스 그룹안에 그 자신이 놓이도록 setpgid를 호출하고, 그다음 전면에 이 프로세스 그룹이 놓이도록 tcsetpgrp를 호출함으로써 이러한 일을 한다.

쉘이 작업제어가 가능하게 되었을 때, 작업제어를 멈추도록 하는 모든 시그널에 대해서 무시하도록 그 자체에서 설정하기 때문에 그 자체는 우연히 멈추게 되지 않는다. 당신은 모든 멈춤 시그널들을 SIG_IGN 으로 설정하여 이러한 일을 할 수 있다.

비-대화식으로 실행되는 서브쉘은 작업제어가 지원되지도 않고 지원될 수도 없다. 그것은 그자체가 쉘인 프로세스 그룹안에 만든 모든 프로세스가 존재해야만 한다; 이것은 비-대화실 쉘과 부모 쉘에 의해 단일한 작업으로 취급되는 자식 프로세스를 허용한다. 이것은 작업제어를 사용하지 않지만 그것을 하는 쉘을 만들기 위해서는 기억해야만 한다.

다음은 이러한 모든 것을 어떻게 하는지 보여주는 예제 쉘을 위한 초기 코드이다.

/* 쉘의 속성을 기억하라. */
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>
 
pid_t shell_pgid;
struct termios shell_tmodes;
int shell_terminal;
int shell_is_interactive;
 
/* 진행하기 전에 쉘이 전면작업으로써 대화식으로 실행되고 있는지를 확인하라. */
void
init_shell ()
{
 
/* 만일 대화식으로 실행된다면 보아라. */
shell_terminal = STDIN_FILENO;
shell_is_interactive = isatty (shell_terminal);
 
if (shell_is_interactive)
{
/* 전면에 놓일때까지 루프를 돈다. */
while (tcgetpgrp (shell_terminal) != (shell_pgid = getpgrp()))
kill (- shell_pgid, SIGTTIN);
 
/* 대화와 작업-제어 시그널들을 무시하라. */
signal (SIGINT, SIG_IGN);
signal (SIGQUIT, SIG_IGN);
signal (SIGTSTP, SIG_IGN);
signal (SIGTTIN, SIG_IGN);
signal (SIGTTOU, SIG_IGN);
signal (SIGCHLD, SIG_IGN);
 
/* 우리소유의 프로세스 그룹에 우리자신을 넣어라. */
shell_pgid = getpid ();
if (setpgid (shell_pgid, shell_pgid) < 0) {
perror("Couldn't put the shell in its own process group");
exit (1);
}
 
/* 터미널의 제어를 잡아라 */
tcsetpgrp (shell_terminal, shell_pgid);
 
/* 쉘을 위한 디폴트 터미널 속성을 저장하라. */
tcgetattr (shell_terminal, &shell_tmodes);
}
}

 

24.6.3 작업들을 개시하기

일단 쉘이 터미널을 제어하여서 작업제어를 수행하는 책임을 갖게 되면, 쉘은 사용자에의해 입력된 명령문에 응답하여 작업을 개시할 수 있다. 프로세스 그룹에서 프로세스들을 만들기 위해서는, 23.2절 [Process Creation Concepts] 에 설명된 fork 와 exec를 사용한다. 그곳에는 여러개의 자식 프로세스들이 이미 존재하고 있기 때문에, 그 일을 하기는 좀 많이 복잡하고 당신은 올바른 순서로 프로세스를 만들도록 주의를 해야만 한다. 그렇지 않다면, 고약한 경쟁상황(race condition)이 발생될 것이다.

당신은 프로세스들 사이의 부모-자식 관계를 어떻게 구성할것인지에 대해서 두가지를 선택할 수 있다. 프로세스 그룹안에 있는 모든 프로세스들을 쉘 프로세스의 자식 프로세스로 만들거나, 또는 그룹에 있는 한 개의 프로세스를 그 그룹에 있는 다른 모든 프로세스들의 조상이 되도록 만들수 있다. 이 장에 설명된 예제 쉘 프로그램은 첫 번째가 좀 간단하게 때문에 첫 번째를 사용한다.

만들어진 각각의 프로세스는, setpid를 호출하여 새로운 프로세스 그룹에 그 자신을 넣어야만 한다; 24.7.2절 [Process Group Functions] 참조. 새로운 그룹에 있는 첫 번째 프로세스는 그 프로세스 그룹의 리더가 되고 그 프로세스 ID가 그 그룹을 위한 프로세스 그룹 ID가 된다.

쉘은 새로운 프로세스 그룹으로 각각의 자식 프로세스들을 넣기 위해서 setpgid를 호출한다. 이것은 타이밍 문제가 내포되어 있다; 각각의 자식 프로세스는 새로운 프로그램이 실행되기 전에 프로세스 그룹에 들어가야만 하고, 쉘은 실행을 계속하기 전에 그룹이 가지고 있는 모든 자식 프로세스에 의존한다. 만일 자식 프로세스와 쉘이 모두 setpgid를 호출한다면, 이것은 프로세스가 먼저 그 일을 처리하도록 해서 아무런 문제가 발생되지 않을 것이다.

만일 전면 작업으로 어떤 작업이 진행되고 있다면, 새로운 프로세스 그룹을 tcsetpgrp를 사용해서 터미널을 제어하고 있는 전면으로 넣을 필요가 있다. 다시, 이것은 경쟁 상황을 피하기 위해서 각각의 자식 프로세스 뿐만아니라, 쉘에 의해서도 수행된다. 각 자식 프로세스의 다음 일은 시그널동작을 재설정하는 일이다.

초기화 하는동안, 쉘 프로세스는 작업제어 시그널들을 무시하도록 그 자신을 설정한다; 24.6.2절 [Initializing the Shell] 참조. 그 결과로, 그것이 만든 어떤 자식 프로세스도 또한 상속으로 인해서 그러한 시그널들을 무시하게 된다. 이것에 만족하지 못하면, 각각의 자식 프로세스는 그것이 만들어진 후에 SIG-DFL을 사용해서 그 시그널의 원래 동작으로 되돌려 설정할 수 있다.

쉘이 이러한 관습을 따르기 때문에, 응용 프로그램은 그들이 부모 프로세스로부터 시그널의 처리에 대한 것을 정확히 상속받는다고 가정 할 수 있다. 그러나 모든 응용프로그램은 멈춤 시그널들의 처리에 간섭할 수 없다. SUSP 문자의 일반적인 해석이 불가능하게 만든 응용프로그램은 사용자가 작업을 멈추게 하기 위해서 다른 메카니즘을 제공한다. 사용자가 이 메카니즘을 호출할 때, 그 프로그램은 단지 프로세스 그 자체가 아니라, 그 프로세스의 프로세스 그룹에게 SIGTSTP 시그널을 보낸다. 21.6.2절 [Signaling Another Process] 참조.

마침내, 각각의 자식 프로세스는 보통의 방법으로 exec를 호출하여야 한다. 이것은 표준 입/출력 채널들의 리다이렉션이 처리되는 지점이다. 8.8절 [Duplicating Descriptors] 를 참조하여 이것을 어떻게 하는지 보아라. 다음은 프로그램을 개시하기 위한, 대화식 쉘 프로그램에 있는 함수이다. 그 함수는 쉘에 의해서 자식 프로세스가 만들어진 후에 즉시 자식 프로세스에 의해서 실행되고 결코 리턴하지 않는다.

void
launch_process (process *p, pid_t pgid, int infile, int outfile, int errfile, int foreground)
{
pid_t pid;
 
if (shell_is_interactive)
{
/* 프로세스 그룹에 그 프로세스를 넣고 터미널을 프로세스 그룹에게 주어라, 적당하면, 이것은 쉘과 개별적인 자식 프로세스에 의해서 모두 실행되기 때문에 경쟁상황을 포함하고 있다. */
pid = getpid ();
if (pgid == 0) pgid = pid;
setpgid (pid, pgid);
if (foreground)
tcsetpgrp (shell_terminal, pgid);
 
/* 작업제어 시그널위한 처리를 디폴트로되돌려 설정하라. */
signal (SIGINT, SIG_DFL);
signal (SIGQUIT, SIG_DFL);
signal (SIGTSTP, SIG_DFL);
signal (SIGTTIN, SIG_DFL);
signal (SIGTTOU, SIG_DFL);
signal (SIGCHLD, SIG_DFL);
}
 
/* 새로운 프로세스를 위한 표준 입/출력 채널들을 설정하라. */
if (infile != STDIN_FILENO)
{
dup2 (infile, STDIN_FILENO);
close (infile);
}
if (outfile != STDOUT_FILENO)
{
dup2 (outfile, STDOUT_FILENO);
close (outfile);
}
if (errfile != STDERR_FILENO)
{
dup2 (errfile, STDERR_FILENO);
close (errfile);
}
 
/* 새로운 프로세스를 실행하라. exit를 확인하라. */
execvp (p->argv[0], p->argv);
perror ("execvp");
exit (1);
}

만일 쉘이 대화식으로 실행되지 않으면, 이 함수는 프로세스 그룹이나 시그널로 아무일도 하지 않는다. 작업제어를 수행하지 않는 쉘은 그 자체가 쉘인 프로세스 그룹안에 모든 서브프로세스들이 존재해야만 한다.

다음은 실제로 완전한 작업을 시작하는 함수이다. 자식 프로세스가 만들어진 후에, 이 함수는 전면이나 배경으로 새로이 만들어진 작업을 넣기위해서 어떤 다른 함수를 호출한다; 그것은 24.6.4절 [Foreground and Background] 에 설명되어 있다.

void
launch_job (job *j, int foreground)
{
process *p;
pid_t pid;
int mypipe[2], infile, outfile;
 
infile = j->stdin;
for (p = j->first_process; p; p = p->next)
{
/* 만일 필요하면 pipe들을 준비하라. */
if (p->next) {
if (pipe (mypipe) < 0) {
perror ("pipe");
exit (1);
}
outfile = mypipe[1];
} else {
outfile = j->stdout;
}
 
/* 자식 프로세스들을 만들어라 */
pid = fork ();
if (pid == 0) { /* 이것은 자식 프로세스이다. */
launch_process (p, j->pgid, infile, outfile, j->stderr, foreground);
} else if (pid < 0) {
/* fork 가 실패했다. */
perror ("fork");
exit (1);
} else { /* 이것은 부모 프로세스이다. */
p->pid = pid;
if (shell_is_interactive) {
if (!j->pgid)
j->pgid = pid;
setpgid (pid, j->pgid);
}
}
 
/* pipe들을 정리하라. */
if (infile != j->stdin)
close (infile);
if (outfile != j->stdout)
close (outfile);
infile = mypipe[0];
}
 
format_job_info (j, "launched");
 
if (!shell_is_interactive)
wait_for_job (j);
else if (foreground)
put_job_in_foreground (j, 0);
else
put_job_in_background (j, 0);
}

 

24.6.4 전면 과 배경

이제 전면(foreground)에 있는 작업을 시작할 때 쉘에 의해서 행해져야만 하는 동작은 무엇이며, 배경작업이 시작될 때 해야만 되는 것과 무엇이 어떻게 다른지 알아보자. 전면작업이 시작될 때, 쉘은 첫 번째로 tcsetpgrp를 호출해서 그 전면작업에 터미널 제어권을 줘야만 한다. 그리고나서, 쉘은 프로세스 그룹안에 있는 프로세스들이 종료되거나 멈출때까지 기다려야한다. 이것에 대한 상세한 내용은 24.6.5절 [Stopped and Terminated Jobs] 를 참조하라.

그룹안에 있는 모든 프로세스들이 수행됐거나 멈추었을 때, 쉘은 다시 tcsetpgrp를 호출해서 자신의 프로세스 그룹을 위해서 터미널 제어권을 되찾는다. 배경작업으로부터의 입/출력이나 사용자에의해 입력된 SUSP 문자가 원인이 된 멈춤 시그널이 프로세스 그룹에게 보내어지면, 보통, 작업안에 있는 모든 프로세스들이 함께 멈춘다.

전면작업이 터미널을 이상한 상황으로 만들었을지도 모르므로, 쉘은 계속하기전에 자신이 저장해놓았던 터미널 모드들을 반환하여야한다. 작업이 완전히 멈춘경우에, 쉘은 일단 현재 터미널 모드들을 저장하고, 그래서 만일 그 작업이 나중에 다시 계속된다면 그들을 반환할 수 있다. 터미널 모드들을 다루는 함수들은 tcgetattr 과 tcsetattr이 있다; 그들은 12.4절 [Terminal Modes] 를 참조하라. 다음은 위에 설명된 일들을 하는 예제 쉘을 함수이다.

/* 작업 j를 전면에 놓는다. 만일 cont가 0이 아니면, 저장된 터미널 모드들을 반환하고 우리가 블록하기 전에 그것이 계속되도록 SIGCONT 시그널을 프로세스 그룹에게 보낸다. */
void
put_job_in_foreground (job *j, int cont)
{
/* 전면으로 작업을 놓는다. */
tcsetpgrp (shell_terminal, j->pgid);
 
/* 만일 필요하다면 작업을 계속하도록 시그널을 보낸다. */
if (cont) {
tcsetattr (shell_terminal, TCSADRAIN, &j->tmodes);
if (kill (- j->pgid, SIGCONT) < 0)
perror ("kill (SIGCONT)");
}
 
/* 보고하도록 기다린다. */
wait_for_job (j);
 
/* 전면에 쉘을 다시 놓아라. */
tcsetpgrp (shell_terminal, shell_pgid);
 
/* 쉘의 터미널 모드들을 반환한다. */
tcgetattr (shell_terminal, &j->tmodes);
tcsetattr (shell_terminal, TCSADRAIN, &shell_tmodes);
}
만일 프로세스 그룹이 배경작업으로써 시작된다면, 쉘은 전면에 자신을 놓고 터미널로부터 명령문 읽기를 계속한다. 예제 쉘에서는, 배경에 작업을 놓아야 될 필요가 그다지 없다. 다음은 그것을 사용하는 함수이다.
/* 배경에 작업을 놓는다. 만일 cont 인수가 참이면, 그것이 계속되도록 SIGCONT 시그널을 프로세스 그룹에게 보낸다. */
void
put_job_in_background (job *j, int cont)
{
/* 만일 필요하면, 작업을 계속하도록 시그널을 보낸다. */
if (cont)
if (kill (-j->pgid, SIGCONT) < 0)
perror ("kill (SIGCONT)");
}

 

24.6.5 멈추고 종료된 작업들

전면 프로세스가 시작될 때, 쉘은 작업에 있는 모든 프로세스들이 멈추거나 종료될때까지 블록해야만 한다. 그것은 waitpid 함수를 호출함으로써 이루어진다; 23.6절 [Process Completion] 참조. 프로세스가 종료된것뿐만 아니라 멈춤 또한 보고하도록 WUNTRACED 옵션을 사용하라. 쉘은 배경작업이 멈추었거나 종료된 작업들을 사용자에게 보고하도록 배경작업들의 상황을 체크해야만 한다; 이것은 WNOHANG 옵션을 사용해서 waitpid 함수를 호출함으로써할 수 있다. 작업이 멈추었거나 종료되었음을 체크하는것과 같은 코드문을 넣는 좋은 위치는 새로운 명령문을 읽으려(prompting) 하기 전이다.

쉘은 SIGCHLD 시그널을 처리하는 핸들러를 만듦으로써 자식 프로세스를 위한 유용한 상황정보를 비동기적으로 통지받을 수 있다. 21장 [Signal Handling] 참조.

예제 쉘 프로그램의 경우에는, SIGCHLD 시그널은 보통 무시된다. 쉘이 조작하고 있는 전역 데이터 구조체를 재진입하는 문제를 피하기 위함이다. 그러나 쉘이 그들 데이터 구조체를 사용하지 않는 어떤 시간동안에는 _예를들어, 터미널에서 입력을 기다리는 때와 같은_SIGCHLD 시그널을 처리하는 핸들러는 가능하다. 동기적으로 상황들을 체크하는데 사용되는 같은 함수는 (이 경우, do_job_notification) 이 핸들러로부터 호출될 수 있다.

다음은 작업의 상황들을 체크하고 사용자에게 정보를 보고하는 것을 취급하는 예제 쉘 프로그램의 일부분이다.

/* waitpid에 의해 리턴된 프로세스 pid의 상황을 저장하라. 모두 잘 됐으면 0을 리턴하고, 아니면 0이 아닌값을 리턴하라. */
 
int
mark_process_status (pid_t pid, int status)
{
job *j;
process *p;
 
if (pid > 0) {
/* 프로세스를 위해서 기록을 갱신하라. */
for (j = first_job; j; j = j->next) {
for (p = j->first_process; p; p = p->next) {
if (p->pid == pid) {
p->status = status;
if (WIFSTOPPED (status)) {
p->stopped = 1;
} else {
p->completed = 1;
if (WIFSIGNALED (status))
fprintf(stderr, "%d: Terminated by signal %d.\n", (int)pid, WTERMSIG(p->status));
}
return 0;
}
fprintf (stderr, "No child process %d.\n", pid);
return -1;
} /* for 의 끝 */
} /* for 의 끝 */
} else if (pid == 0 || errno == ECHILD) {
/* 보고하기위해 준비된 프로세스가 없다. */
return -1;
} else { /* Other weird errors. */
perror ("waitpid");
return -1;
}
}
 
/* 블록하지지 않고, 유용한 상황정보를 가진 프로세스들을 체크하라. */
 
void
update_status (void)
{
int status;
pid_t pid;
 
 
do {
pid = waitpid(WAIT_ANY, &status, WUNTRACED|WNOHANG);
} while (!mark_process_status (pid, status));
}
 
/* 주어진 작업안에 있는 모든 프로세스들이 보고되었을때까지 블록하여, 유용한 상황정보를 가진 프로세스들을 체크하라. */
 
void
wait_for_job (job *j)
{
int status;
pid_t pid;
 
do {
pid = waitpid (WAIT_ANY, &status, WUNTRACED);
} while (((!mark_process_status (pid, status))
&& (!job_is_stopped (j))
&& (!job_is_completed (j)));
}
 
/* 사용자에게 보이기위한 작업상황에 대한 정보를 형식화하라. */
 
void
format_job_info (job *j, const char *status)
{
fprintf(stderr, "%ld (%s): %s\n", (long)j->pgid, status, j->command);
}
 
/* 멈추었거나 종료된 작업들에 대해서 사용자에게 보고하라. 활성화된 작업 리스트로부터 종료된 작업들을 지워라. */
 
void
do_job_notification (void)
{
job *j, *jlast, *jnext;
process *p;
 
/* 자식 프로세스를 위한 상황정보들을 갱신하라. */
update_status ();
 
jlast = NULL;
for (j = first_job; j; j = jnext)
{
jnext = j->next;
 
/* 만일 모든 프로세스들이 완수되었다면, 작업들이 완수되었음을 사용자에게 알리고 활성 작업 리스트에서 그것을 지워라. */
if (job_is_completed (j)) {
format_job_info (j, "completed");
if (jlast)
jlast->next = jnext;
else
first_job = jnext;
free_job (j);
} else if (job_is_stopped (j) && !j->notified) {
/* 더 이상 진행할 수 없음을 표시하여, 멈추어진 작업에 대하여 사용자에게 통지하라. */
format_job_info (j, "stopped");
j->notified = 1;
jlast = j;
} else /* 여전히 실행되고 있는 작업들에 대해서는 아무것도 말하지 말아라. */
jlast = j;
}
}

 

24.6.6 멈추어있는 작업들을계속 실행시키기

쉘은 프로세스 그룹에게 SIGCONT 시그널을 보냄으로써 멈추어진 작업을 계속하게 할 수 있다. 만일 작업이 전면에서 계속 실행되어야하면, 쉘은 일단 tcsetpgrp 를 호출해서 터미널 제어권을 그 작업에게 부여하고, 저장된 터미널 설정을 반환한다. 전면에서 작업이 재개된 후에, 쉘은 그 작업이 마치 전면에서 시작된것처럼, 그 작업이 멈추거나 종료되기를 기다린다.

예제 쉘 프로그램에서 새로이 만들어진 작업과 재개된 작업들에 대한 처리는 put_job_in_foreground 와 put_job_in_background 한쌍의 함수로 이루어진다. 그들 함수에 대한 정의는 24.6.4절 [Foreground and Background] 에 나와있다. 멈추었던 작업을 재개할 때, cont 인수에 주어진 0이 아닌값은 SIGCONT 시그널을 보내고 적당한 터미널 모드를 반환함을 확실하게 한다.

다음은 재개되어 실행되고 있는 작업에 대해서 쉘이 내부적으로 보유하고 있던 정보를 갱신하는 함수이다.

/* 멈추어있던 작업 J를 다시 실행되고 있는 작업으로써 표시하라. */
 
void
mark_job_as_running (job *j)
{
Process *p;
 
for (p = j->first_process; p; p = p->next)
p->stopped = 0;
j->notified = 0;
}
 
/* 작업 J를 계속하라. */
 
void
continue_job (job *j, int foreground)
{
mark_job_as_running (j);
if (foreground)
put_job_in_foreground (j, 1);
else
put_job_in_background (j, 1);
}

 

24.6.7 이 장에서 설명되지 않은 부분들

이장에 포함된 예제 쉘을 위한 여분의 코드는 전체 쉘 프로그램을 위한 일부분이다. 하지만, 어떻게 작업과 프로그램 데이터 구조체들이 할당되고 초기화되는지에 대해서는 전혀 얘기하지 않았다. 대부분의 실제 쉘은 명령문; 변수들; 약어, 대입, 그리고 파일이름의 패턴 매칭등을 위한 것을 제공하는, 복잡한 사용자 인터페이스로 되어있다. 이곳에 설명된 이 모든 것은 복잡함과는 거리가 멀다! 대신에, 우리는 어떻게 코아 프로세스를 생성을 하는지를 보여주고 그와 같은 쉘에서 어떤 작업제어 함수들이 호출될 수 있는지에 대해서 관심을 두었다. 다음은 우리가 표현했던 주요 엔트리 포인트를 요약한 테이블이다.

void init_shell (void)

쉘의 내부 상황을 초기화한다. 24.6.2절 [Initializing the Shell] 참조.

void init_sh

void launch_job (job *j, int foreground)

전면 또는 배경에서 작업 j를 시작한다. 24.6.3절 [Launching Jobs] 참조.

void do_job_notification (void)

종료되거나 멈추어진 어떤 작업들이 있는지 체크하고 보고한다. SIGCHLD 시그널을 위한 핸들러 안에서 또는 동기적으로 호출될 수 있다. 24.6.5절 [Stopped and Terminated Jobs] 참조.

void continue_job (job *j, int foreground)

작업 j를 재개한다. 24.6.6절 [Continuing Stopped Jobs] 참조.
물론, 실제 쉘은 작업을 처리하기 위해서 다른 함수들을 제공받기 원할지 모른다. 예를들어, 활성화된 모든 작업들의 리스트를 보여주거나, 작업에게 시그널(SIGKILL 과 같은)을 보내는 명령문은 유용할 것이다.


24.7 작업제어를 위한 함수들

이 절은 작업제어와 연관된 함수들을 자세하게 설명하고 있다.

 

24.7.1 제어중인 터미널 확인하기

당신은 제어중인 터미널을 개방하기 위해서 사용할 수 있는 파일이름을 얻기 위해서 ctermid 함수를 사용할 수 있다. GNU 라이브러리에서는, 항상 같은 문자열이 리턴된다: "/dev/tty". 그것은 현재 프로세스의 제어 중인 터미널(만일 그것이 한 개라면)을 참조하기위한 "이상한" 특별파일 이름이다. ctermid 함수는 헤더파일 `stdio.h'에 선언되어 있다.

함수 : char * ctermid (char *string)

ctermid 함수는 현재 프로세스를 위해서 제어중인 터미널의 파일이름이 포함된 문자열을 리턴한다. 만일 문자열이 널 포인터가 아니라면, 그것은 적어도 L_ctermid 문자들을 저장하고 있는 배열이 될 것이다. 문자열을 이 배열안에 리턴된다. 그렇지 않다면, 정적 지역안에 리턴된 문자열에 대한 포인터는 이 함수가 연속적으로 호출되어 덧씌워질 것이다.
만일 어떠한 이유로 파일이름이 결정될 수 없다면 빈 문자열(empty string)을 리턴한다. 심지어 파일이름이 리턴됐을지라도, 그 파일에 대한 억세스는 보증되지 않는다.

매크로 : int L_ctermid

이 매크로는 ctermid에 의해 리턴된 파일이름을 저장하기에 충분히 큰 문자열의 크기를 나타내는 정수 상수 표현식이다.

또한 12.1절 [Is It a Terminal] 에 있는 isatty 와 ttyname 함수를 참조하라.

 

24.7.2 프로세스 그룹 함수들

다음은 프로세스 그룹을 다루기 위한 함수를 설명하고 있다. 당신의 프로그램에서 그들 함수들을 사용하기 위해서는 헤더파일 `sys/types.h' 와 `unistd.h'를 포함해야만 한다.

함수 : pid_t setsid (void)

setsid 함수는 새로운 세션을 만든다. 호출한 프로세스는 세션리더가 되고, 그 프로세스의 프로세스 ID와 같은 ID를 가진 새로운 프로세스 그룹이 만들어지고 프로세스가 그 안에 소속된다. 새로운 프로세스 그룹에 다른 프로세스들이 없고 새로운 세션안에 다른 프로세스 그룹이 없이 초기화된다. 이 함수는 또한 호출한 프로세스가 제어중인 터미널을 갖지 않도록 만든다.
 
setsid 함수는 만일 성공하면 호출한 프로세스의 새로운 프로세스 그룹 ID를 리턴하고 실패하면 -1의 값을 리턴한다.  
다음은 이 함수를 위해 정의된 에러상황이다.

    EPERM

    호출한 프로세스가 이미 프로세스 그룹의 리더이거나, 같은 프로세스 그룹 ID를 가진 다른 프로세스 그룹이 있다.
 
getpgrp 함수는 두 개의 정의를 가진다; 한 개는 BSD 유닉스에서 왔고, 한 개는 POSIX.1 표준으로부터 온 것이다. 당신이 선택한 테스트 매크로(1.3.4절 [Feature Test Macros] 참조.)에 따라서 당신이 어떤 정의를 선택했는지가 결정된다. 즉, 당신이 _BSD_SOURCE를 정의하면 BSD 것이 선택되고; 그렇지않고, _POSIX_SOURCE 또는 _GNU_SOURCE를 정의한다면 POSIX의 것이 선택된 것이다. 오래된 BSD 시스템에서 만들어진 프로그램들은 _BSD_SOURCE 하에서 특별히 정의된 getgrp를 사용하도록 `unistd.h'를 인클루드 할 수없다. 당신은 BSD정의를 획득 하기 위해서 -lbsd-compact 로 그와 같은 프로그램들을 링크해야만 한다.

    POSIX.1 함수 : pid_t getpgrp (void)

    getpgrp의 POSIX.1 정의는 호출한 프로세스의 프로세스 그룹 ID를 리턴한다.

    BSD 함수 : pid_t getpgrp (pid_t pid)

    getpgrp 의 BSD 정의는 프로세스 pid의 프로세스 그룹 ID를 리턴한다. 당신이 호출한 프로세스에 대한 정보를 얻기위해서는 pid인수를 위해서 0의 값을 사용할 수 있다.

함수 : int setpgid (pid_t pid, pid_t pgid)

setpgid 함수는 프로세스 그룹 pgid에 프로세스 pid를 소속시킨다. 특별한 경우로, pid 또는 pgid는 호출한 프로세스의 프로세스 그룹을 지적하기 위해서 0으로 될 수 있다. 이 함수는 작업제어를 지원하지 않는 시스템에서는 실패한다. 24.2절 [Job Control is Optional] 참조하여 상세한 정보를 얻어라.
 
만일 명령이 성공하면, setpgid는 0을 리턴한다. 그렇지 않다면 -1을 리턴한다.  
다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EACCES : pid라는 이름을 가진 자식 프로세스가 만들어진 후에 exec 함수를 통해서 실행되었다.

EINVAL : pgid의 값이 유용하지 않다.

ENOSYS : 시스템이 작업제어를 지원하지 않는다.

EPERM

pid 인수에 의해 지적된 프로세스가 세션리더이거나, 호출한 프로세스와 같은 세션에 없거나, pgid 인수의 값이 호출한 프로세스와 같은 세션안에 있는 프로세스 그룹 ID와 매치되는 것이 없다.

ESRCH

pid 인수에 의해 지적된 프로세스가 호출한 프로세스가 아니거나 호출한 프로세스의 자식 프로세스가 아니다.

함수 : int setpgrp(pid_t pid, pid_t pgid)

이것은 setpgid를 위한 BSD용이다. 두 함수는 같은 일을 한다.

 

24.7.3 제어권을 가진 터미널을 억세스하기 위한 함수들

다음은 터미널의 전면프로세스 그룹을 알아내거나 설정하기 위한 함수들이다. 당신은 그들 함수들을 당신의 어플리케이션에서 사용하기 위해서는 헤더파일 `sys/types.h'와 `unistd.h'를 인클루드해야만 한다. 그들 함수들이 터미널 디바이스를 정하도록 파일 기술자 인수를 정한다고 하더라도, 전면작업은 터미널 파일 그자체와 연관되어 있고 특별히 개방된 파일 기술자가 아니다.

함수 : pid_t tcgetpgrp(int filedes)

이 함수는 기술자 filedes로 개방한 터미널과 연관된 전면 프로세스 그룹의 프로세스 그룹 ID를 리턴한다. 만일 아무런 전면 프로세스 그룹이 없다면, 현존하는 프로세스 그룹의 프로세스 그룹 ID와 매치 되지 않는, 1보다 큰 수를 리턴한다. 그러한 경우는 전면작업으로써 수행되고 있던 작업안에 있는 모든 프로세스들이 종료되고 전면으로 옮겨질 작업들이 없을 때 발생할 수 있다
 
에러의 경우에 -1의 값이 리턴된다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EBADF : filedes 인수가 유용한 파일 기술자가 아니다.

ENOSYS : 시스템이 작업제어를 지원하지 않는다.

ENOTTY : filedes 인수와 연관된 터미널 파일이 호출한 프로세스가 제어중인 터미널이 아니다.

함수 : tcsetpgrp(int filedes, pid_t pgid)

이 함수는 터미널의 전면 프로세스 그룹 ID를 설정하는데 사용된다. 인수 filedes는 터미널을 지정하는 기술자이다; pgid 인수는 프로세스 그룹을 지정한다. 호출한 프로세스는 pgid와 같은 세션의 멤버가 되어야만 하고 같은 터미널을 제어하고 있어야만 한다.
 
터미널 억세스를 목적으로 하여, 이 함수는 출력으로 취급된다. 만일 배경 프로세스로부터 이 함수가 호출된다면, 보통 그 프로세스 그룹안에 있는 모든 프로세스들에게 SIGTTOU 시그널이 보내어진다. 만일 호출한 프로세스 자체가 SIGTTOU 시그널을 무시하는 예외의 경우라면, 그 명령은 수행되고 아무런 시그널도 보내어지지 않는다.
 
만일 성공하면, tcsetpgrp 는 0을 리턴한다. 에러가 발생한 경우에는 -1을 리턴한다.
다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EBADF : filedes 인수가 유용한 파일 기술자가 아니다.

EINVAL : pgid 인수가 유용하지 않다.

ENOSYS : 시스템이 작업제어를 지원하지 않는다.

ENOTTY : filedes는 호출한 프로세스가 제어하고 있는 터미널이 아니다.

EPERM : pgid 는 호출한 프로세스와 같은 세션에 있는 프로세스 그룹이 아니다.


목차 이전 : 23. 프로세스 다음 : 25. 사용자와 그룹

'Programming > Unix Programing' 카테고리의 다른 글

26. System Information  (0) 2007.12.22
25. 사용자와 그룹  (0) 2007.12.22
24. Job Control(작업 제어)  (0) 2007.12.22
23. Processes  (0) 2007.12.22
22. 프로세스의 시동과 종료(Process StartUp)  (0) 2007.12.22
21. 신호처리(Signal)  (0) 2007.12.22
Comment 0 Trackback 0
Top

23. Processes

목차 이전 : 22. 프로세스의 시동과 종료 다음 : 24. 작업 제어


23 프로세스

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

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

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

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] 참조.


목차 이전 : 22. 프로세스의 시동과 종료 다음 : 24. 작업 제어

'Programming > Unix Programing' 카테고리의 다른 글

25. 사용자와 그룹  (0) 2007.12.22
24. Job Control(작업 제어)  (0) 2007.12.22
23. Processes  (0) 2007.12.22
22. 프로세스의 시동과 종료(Process StartUp)  (0) 2007.12.22
21. 신호처리(Signal)  (0) 2007.12.22
19. 지역과 세계화  (0) 2007.12.22
Comment 0 Trackback 0
Top

22. 프로세스의 시동과 종료(Process StartUp)

목차 이전 : 21. 시그널 처리 다음 : 23. 프로세스


22 프로세스의 시동과 종료

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

 
** 역자주 : thread(쓰레드) : 프로세스 보다 더 작은 하나의 작업단위.

일반적으로 우리는 하나의 프로세스를 단위로 하여 독립적인 작업을 수행하는 것으로 알고 있지만, 컴퓨터 기술의 발전으로 프로세스라는 단위는 너무나 큰 단위가 되어버리고 이를 대신하여 쓰레드라는 단위가 사용되게 되었다. 쓰레드를 사용하는 운영체제에서는 작업의 스케줄링을 수행할 때 쓰레드를 단위로 하여 스케줄링을 한다.

이 장은 당신의 프로그램에서 프로세스를 시동하고, 그것을 종료시키고, 부모 프로세스로 부터 정보(인수들과 환경)를 받기 위하여 무엇을 해야만 하는지를 설명하고 있다.


22. 1 프로그램 인수들

시스템은 main 함수를 호출함으로써 C 프로그램을 시작한다. 그것은 당신이 프로그램을 만들 때 main 이라 이름지어진 함수를 꼭 만들어야 함을 의미하고_그것을 하지 않았을 때 에러없이 프로그램을 링크할 수 없을 것이다.

main 함수는 아무런 인수를 취하지 않거나, 커멘드 라인에서 받아들이는 인수를 나타내는 두 개의 인수를 취한다. 다음처럼.

    int main (int argc, char *argv[])

커멘드 라인 인수들은 공백으로 분리된 토큰으로써 프로그램을 호출하도록 쉘 커맨드에 주어진다; 그래서, `cat foo bar'라고 했을 때 인수는 `foo' 와 `bar'가 된다. 어떤 프로그램에서 커맨드 라인에 인수를 넣는 방법은 오직 main함수의 인수를 통해서 가능하다. 만일 main 함수가 인수를 취하지 않는다면, 당신은 커맨드 라인에서 인수를 취할 수 없다.

argc 인수는 커맨드 라인 인수들의 개수를 말하고, argv 인수는 C 문자열의 벡터이다; argv의 각 요소들은 공백으로 분리된 개별적인 커맨드 라인 인수 문자열이다. 실행시키려는 프로그램의 파일이름 또한 첫 번째 요소로써 벡터 안에 포함된다. argc의 값은 이 요소들의 개수이다. 널 포인터가 항상 마지막 요소가 된다: 즉, argv[argc]는 널 포인터이다. 커맨드 `cat foo bar' 에서, argc는 3이고, argv는 "cat", "foo", 그리고 "bar" 라는 세 개의 요소들을 갖는다.

만일 당신의 프로그램에서 커맨드 라인 인수들의 구분이 간단하다면, 당신은 직접 argv로부터 간단하게 하나씩 인수를 가져올 수 있다. 당신의 프로그램이 한정된 인수들의 개수를 갖지않거나, 모든 인수들을 한꺼번에 취하지 않는다면 같은 방법으로 인수들을 취할 수 있지만, 그렇지 않다면 그것을 구문분석 하기 위해서 getopt를 사용해서 인수들을 취해야 할 것이다.

 

22. 1. 1 프로그램 인수 구문 관례들

POSIX에서 커맨드 라인 인수들을 위해서 다음과 같은 관례들을 권장하고 있다. getopt (22. 1. 2 [Parsing Options] 참조)는 다음의 권장사항을 쉽게 충족시키도록 만든다.

  • 하이픈(hyphen (`_') ) 구획문자(delimiter)로 시작하는 인수들은 옵션이 있다.
  • 한 개의 토큰에서 한 개의 하이픈 구획문자 뒤에 따르는 것은, 만일 그 옵션들이 인수들을 취하지 않는다면 다중 옵션이 된다. 그래서, `-abc'는 `-a -b -c'와 동등하다.
  • 옵션 이름들은 단일한 알파벳 문자들이다( isalnum에 관하여; 4. 1절[Classification of Characters] 참조)
  • 어떤 옵션들을 인수를 필요로 한다. 예를 들어, ld 명령의 `-o' 옵션은 출력 파일 이름으로 하나의 인수를 필요로 한다.
  • 옵션과 옵션이 필요로 하는 인수들은 토큰으로 분리되어 나타날 수도 있고, 그렇지 않을 수도 있다. (즉, 그들을 분리하는 공백문자는 선택적이다. ) 그래서, `-o foo' 와 `-ofoo'는 동등한다.
  • 옵션들은 전형적으로 다른 비-옵션 인수들에 선행된다.

GNU C 라이브러리에 있는 getopt의 구현은 당신의 프로그램을 사용하는 사용자가 심지어 옵션과 비-옵션 인수들을 혼합했을지라도, 구문분석을 위해서 모든 비-옵션 인수들 전에 옵션 인수들이 나타나도록 만든다.

인수 `--'는 모든 옵션들을 종결시킨다; 다음에 따르는 인수들은 그들이 하이픈으로 시작할지라도, 비-옵션 인수들로 취급된다.

한 개의 하이픈 문자가 들어가 있는 토큰은 보통 비-옵션 인수로써 취급된다. 관례상, 그것은 기본 입력과 출력 스트림으로 입력이나 출력을 정하기 위해서 사용된다.

옵션들은 어떤 순서대로 공급되어지고, 또는 여러번 중복해서 나타나기도 할 것이다. 해석은 특정한 응용 프로그램이 맨 왼쪽에 있다고 해석한다.

GNU 는 그들의 관례에 따라서 긴 옵션을 더한다. 긴 옵션들은 `--'뒤에 따르는 이름으로 구성되는데 그 이름은 영숫자와 대쉬들로 구성된다. 옵션 이름들은 전형적으로 하나에서 세 개 단어의 길이를 갖고, 그들은 하이픈으로 단어들을 분리한다. 사용자들은 옵션을 대표하는 약자가 단일하다면 얼마든지 옵션이름들을 축약할 수 있다. 한 개의 긴 옵션을 위한 인수를 정하기 위해서는, `--name=value'라고 써라. 이 구문은 선택적인 인수를 받아들이는 긴 옵션을 가능하게 한다. GNU 시스템은 쉘에서 긴 옵션 이름을 지원할 것이다.

 

22. 1. 2 프로그램 옵션들을 구문 분석하기

이번에는 getopt 함수를 어떻게 호출하는지에 대해서 자세히 설명한다. 이 기능을 사용하기 위해서, 당신의 프로그램에 헤더파일 `unistd. h'를 포함시켜야만 한다.

변수 : int opterr

만일 이 변수의 값이 0이 아니면, getopt는 만일 알지 못하는 옵션 문자가 들어오거나 인수를 필요로 하는 옵션이 옵션을 갖고있지 않다면 표준 에러 스트림에 에러메세지를 프린트한다. 이것은 디폴트 동작이다. 만일 당신이 이 변수를 0으로 설정하면, getopt는 아무런 메시지도 프린트하지는 않지만, 에러를 지적하기 위해서 여전히 ? 문자를 여전히 반환한다.

변수 : int optopt

getopt가 알려지지 않은 옵션 문자와 만나거나 필요한 인수가 빠져있는 옵션일 때, getopt는 그 옵션 문자를 이 변수에 저장한다. 당신은 자신만의 진단 메시지를 제공하기 이해서 이 변수를 사용할 수 있다.

변수 : int optind

이 변수는 앞으로 분석할 배열 argv의 다음 요소에 대한 인덱스로 getopt에 의해 설정된다. 일단 getopt가 옵션 인수들 모두를 발견했다면, 당신은 나머지 비-옵션 인수들이 어디에서 시작하는지 결정하기 위해서 이 변수를 사용할 수 있다. 이 변수의 초기값은 1이다.

변수 : chat *optarg

이 변수는 인수들을 받아들이는 옵션들을 위해서, 옵션 인수들의 값을 가리키는 포인터로써 getopt에 의해 설정된다.

함수 : int getopt(int argc, char **argv, const char *options)

getopt 함수는 argv와 argc 인수들에 의해 지정된 인수 목록으로부터 다음 옵션 인수를 얻는다. 보통, 그들 값들은 main에 의해 받아들여진 인수들로부터 직접적으로 온다. 옵션 인수는 이 프로그램에서 유용하게 쓰일 옵션 문자들을 지정한 문자열이다. 문자열 안에 있는 옵션 문자는 인수가 필요함을 알리기위해서 콜론(`: ')의 다음에 나타난다.

만일 옵션 인수 문자열이 하이픈(`-')으로 시작한다면, 이것은 특별하게 취급된다. 그들은 옵션 문자가 `\0'인 것처럼 반환되어, 옵션이 없는 인수들을 허용한다. getopt 함수는 다음 커맨드 라인옵션의 옵션 문자를 반환한다. 더 이상 유용한 옵션 인수들이 없을 때, -1을 반환한다. 그곳은 이제 비-옵션 인수들이 있을 것이다; 당신은 이것을 체크하기 위해서 argc 파라미터에 대응하는 외부변수 optind를 비교해야만 한다.

만일 어떤 옵션이 인수를 가진다면, getopt 는 변수들 optarg안에 그것을 저장함으로써 인수를 반환한다. 당신이 덧씌워질지도 모르는 정적 변수가 아닌, 보통의 argv 배열을 가리키는 포인터를 라면, optarg 문자열을 복사할 필요가 없다.

만일 알려지지 않은 옵션문자를 사용하거나, 옵션 인수가 빠져있다면 getopt는 `?'를 반환하고 현재 옵션 문자로 외부변수 optopt를 설정한다. 만일 옵션의 첫 번째 문자가 콜론(`: ') 이라면, getopt는 옵션 인수가 빠져있음을 알리기위해서 `?' 대신에 `: '를 반환한다. 또한, 만일 외부변수 opterr이 0이 아니면(디폴트이다. ), getopt는 에러 메시지를 출력한다.

 

22. 1. 3 getopt를 사용해서 인수를 구문 분석하는 예제

다음은 getopt가 전형적으로 어떻게 사용되는지를 보여주는 예제이다. 핵심은 다음과 같다:

보통, getopt는 루프 안에서 호출된다. getopt가 더 이상 나타난 옵션이 없음을 알리기위해서 -1을 반환 하면, 루프는 종료한다.

switch 구문은 getopt로부터의 반환값에 따른 각각의 처리에 사용된다. 각각의 경우는 프로그램에서 나중에 사용될 변수를 설정하게 된다.

두 번째 루프는 남아있는 비-옵션 인수들을 처리하는데 사용된다.

#include <unistd. h>
#include <stdio. h>
 
int
main (int argc, char **argv)
{
int aflag = 0;
int bflag = 0;
char *cvalue = NULL;
int index;
int c;
 
opterr = 0;
 
while ((c = getopt (argc, argv, "abc: ")) != -1) {
switch (c)
{
case 'a':
aflag = 1;
break;
case 'b':
bflag = 1;
break;
case 'c':
cvalue = optarg;
break;
case '?':
if (isprint (optopt))
fprintf(stderr, "Unknown option `-%c'. \n", optopt);
else
fprintf (stderr,
"Unknown option character `\\x%x'. \n", optopt);
return 1;
default:
abort ();
}
}
printf("aflag = %d, bflag = %d, cvalue = %s\n", aflag, bflag, cvalue);
 
for (index = optind; index < argc; index++)
printf ("Non-option argument %s\n", argv[index]);
return 0;
}
 
다음은 위의 프로그램을 여러 가지 인수들의 조합을 사용했을 때 어떤 결과를 나타내는지에 대한 예이다.
 
% testopt
aflag = 0, bflag = 0, cvalue = (null)
 
% testopt -a -b
aflag = 1, bflag = 1, cvalue = (null)
 
% testopt -ab
aflag = 1, bflag = 1, cvalue = (null)
 
% testopt -c foo
aflag = 0, bflag = 0, cvalue = foo
 
% testopt -cfoo
aflag = 0, bflag = 0, cvalue = foo
 
% testopt arg1
aflag = 0, bflag = 0, cvalue = (null)
Non-option argument arg1
 
% testopt -a arg1
aflag = 1, bflag = 0, cvalue = (null)
Non-option argument arg1
 
% testopt -c foo arg1
aflag = 0, bflag = 0, cvalue = foo
Non-option argument arg1
 
% testopt -a -- -b
aflag = 1, bflag = 0, cvalue = (null)
Non-option argument -b
 
% testopt -a -
aflag = 1, bflag = 0, cvalue = (null)
Non-option argument -

 

22. 1. 4 긴 옵션들을 구문 분석하기

단일한-문자 옵션들로만 되어있는 GNU-스타일의 긴 옵션들을 받아들이기 위해서는 getopt 대신에 getopt_long를 사용한다. 당신은 이런 가외의 작은 작업을 통해서 모든 프로그램이 긴 옵션들을 받아들이게 만들 수 있고, 초보자들에게 어떻게 그 프로그램을 사용하는지 도울 수 있다.

데이터 타입 : struct option

이 구조체는 getopt_long을 위한 단일한 긴 옵션 이름을 설명한다. 긴 옵션을 구성하는 옵션의 하나인 인수 longopts는 그들 구조체의 배열이 되어야만 한다. 그 배열은 0으로 채워진 요소로써 끝난다. struct option 구조체는 다음과 같은 필드를 갖는다.

const char *name

이 필드는 옵션의 이름으로 문자열이다.

int has_arg

이 필드는 옵션이 인수를 취하는지 또는 취하지 않는지의 여부를 알린다. 그것은 정수이고 그곳에 세 개의 합리적인 값들이 있다: no_argument, required argument, 그리고 optional_argument.

int *flag

int val

이 필드들은 그 옵션이 발생할 때 옵션에 대하여 어떻게 보고를 하거나 동작할 것인지를 제어한다. 만일 flag가 널 포인터라면, val은 이 옵션을 대표하는 값이다. 종종 그 값들은 특정한 긴 옵션들의 단일함을 확인하기 위해서 선택되어진다. 만일 플래그가 널 포인터가 아니라면, 그것은 이 옵션을 위한 플래그인 int형 변수의 주소가 되어질 것이다. val안의 값은 나타난 옵션을 지적하도록 플래그 안에 저장하기위한 값이다.

함수 : int getopt__long (int argc, char **argv, const char *shortopts, struct option *longopts, int *indexptr)

벡터 argv ( 길이는 argc이다. )로부터 옵션들을 해독한다. 인수 shortopts는 getopt가 해독할 수 있는 만큼의 짧은 옵션을 나타내고, 인수 longopts는 받아들이기 위한 긴 옵션들을 나타낸다. getopt_long이 짧은 옵션을 만났을 때, 그것은 getopt가 하는것과 같은 일을 한다: 그것은 옵션을 위한 문자 코드를 반환하고, optarg에 옵션 인수들을(만일 그것이한 개를 가진다면) 저장한다.
getopt_long가 긴 옵션을 만났을 때, 그것은 그 옵션을 정의하는 flag 와 val 필드에 기초한 동작들을 취한다.
 
만일 flag가 널 포인터라면, getopt_long 은 발견한 옵션을 지적하도록 val의 내용을 반환한다. 당신이 다른 의미를 가진 옵션을 위해서 val 필드 안에 별개의 값들을 배열하면, 당신은 getopt_long이 반환한 후에 그들 값을 해독할 수 있다. 만일 긴 옵션이 짧은 옵션과 동일하다면, 당신은 짭은 옵션의 문자 코드를 사용할 수 있다.
 
만일 flag가 널 포인터가 아니라면, 단지 프로그램 안에서 flag가 설정된 이 옵션을 의미한다. flag는 당신이 정의한 int형의 변수이다. flag 필드 안에 그 플래그의 주소를 저장하라. flag에 저장하려는 옵션값을 val 필드 안에 저장하라. 이 경우, getopt_long는0을 반환한다.
 
어떤 긴 옵션에서, getopt_long는 배열 longopts안의 옵션 정의에 대한 인덱스를, *indexptr에 저장하여 알린다. 당신은 longopts[*indexptr]. name 어로 옵션의 이름을 얻을 수 있다. 그래서 당신은 그들의 val 필드 안에 있는 값이나, 그들의 인덱스에 의해서 긴 옵션들을 구분할 수 있다. 당신은 플래그를 설정한 긴 옵션들을 이 방법으로 구분할 수 있다.
 
어떤 긴 옵션이 인수를 가질 때, getopt_long는 반환하기 전에 변수 optarg안에 인수값을 저장한다. 어떤 옵션이 아무런 인수를 가지지 않을 때, optarg안의 값은 널 포인터이다. 이것은 공급된 인수가 옵션을 가졌는지 아닌지에 대한 여부를 당신에게 알리는 방법인 것이다. getopt_long는 더 이상 처리할 옵션들이 없을 때, -1을 반환하고, 다음 남아있는 인수의 argv 인덱스를 변수 optind 안에 남긴다.

 

22. 1. 5 긴 옵션들의 구분분석에 대한 예제

#include <stdio. h>
 
/* 플래그는 `--verbose' 에 의해 설정한다. */
static int verbose_flag;
 
int
main (argc, argv)
int argc, char **argv;
{
int c;
 
while (1) {
static struct option long_options[] = {
/* 그들 옵션들은 플래그를 설정한다. */
{"verbose", 0, &verbose_flag, 1},
{"brief", 0, &verbose_flag, 0},
/* 그들 옵션들은 플래그를 설정하지 않는다. 우리는 그들의 인덱스에 의해 그들을 구분한다. */
{"add", 1, 0, 0},
{"append", 0, 0, 0},
{"delete", 1, 0, 0},
{"create", 0, 0, 0},
{"file", 1, 0, 0},
{0, 0, 0, 0}
};
 
/* getopt_long는 이곳에 옵션 인덱스를 저장한다. */
int option_index = 0;
 
c = getopt_long(argc, argv, "abc: d: ", long_options, &option_index);
 
/* 옵션들의 끝을 검출한다. */
if (c == -1)
break;
 
switch (c) {
case 0:
/* 만일 이 옵션이 플래그를 설정하면, 지금 아무런 일도 하지 말아라. */
if (long_options[option_index]. flag != 0)
break;
printf("option %s", long_options[option_index]. name);
if (optarg)
printf(" with arg %s", optarg);
printf("\n");
break;
case 'a':
puts("option -a\n");
break;
case 'b':
puts("option -b\n");
break;
case 'c':
printf("option -c with value `%s'\n", optarg);
break;
case 'd':
printf("option -d with value `%s'\n", optarg);
break;
case '?':
/* getopt_long는 이미 에러 메시지를 프린트했다. */
break;
default:
abort ();
} /* switch 의 끝*/
} /* while 의 끝 */
 
/* 그들이 맞추었던 것으로 `--verbose'와 `--brief'를 보고하는 대신에, 우리는 그들로부터 나온 결과로 마지막 상황을 보고한다. */
if (verbose_flag)
puts ("verbose flag is set");
 
/* 남아있는 코맨드 라인 인수들을( 옵션이 없는) 출력하라. */
if (optind < argc) {
printf ("non-option ARGV-elements: ");
while (optind < argc)
printf ("%s ", argv[optind++]);
putchar ('\n');
}
 
exit (0);
}


22. 2 환경 변수들

프로그램이 실행될 때, 두 가지 방법으로, 실행될 문맥(context)에 대한 정보를 받는다. 첫 번째 방법은 argv와 argc 인수를 메인 함수에서 사용하는 것으로써 이것에 대한 것은 22. 1절 [Program Arguments] 에서 설명하였다. 두 번째 방법은 환경변수를 사용하는 것인데 지금부터 설명할 것이다. argv를 사용하는 방법은 실행될 특정한 프로그램에 지정된 커맨드 라인 인수를 주기 위해서 전형적으로 사용된다. 다른 방법으로, 환경은, 드물게 변경되고 덜 빈번하게 억세스 되는, 많은 프로그램에 의해 분배될 정보를 기억한다.

이 절에서 논의되고 있는 환경변수들은 당신이 쉘에게 커맨드를 보내고 사용될 것을 배정하는 환경변수와 같은 것이다. 쉘로부터 실행된 프로그램들은 쉘로부터 환경변수 모두를 상속받는다. 표준 환경 변수들은 사용자의 홈 디렉토리, 터미널 타입, 현재 지역 등등에 대한 정보를 위해서 사용된다; 당신은 다른 목적으로 부가적인 환경변수를 정의할 수 있다. 모든 환경변수들의 가진 값들의 집합은 집합적으로 환경으로써 알려진다.

환경변수들의 이름은 대소문자를 구분하고 문자 `='를 포함해서는 안된다. 시스템-정의 환경변수들은 항상 대문자이다. 환경변수들의 값은 문자열로써 표현될 수 있는 어떤 것도 가능하다. 값에는 널 문자가 포함 되서는 안된다, 왜냐하면 이것은 문자열의 끝으로 간주되기 때문이다.

 

22. 2. 1 환경 검색

환경변수의 값은 getenv 함수를 통해서 억세스될 수 있다. 이것은 헤더 파일 `stdlib. h'에 선언되어 있다.

함수 : char *getenv (const char name)

이 함수는 환경변수 name의 값인 문자열을 반환한다. 당신은 이 문자열을 갱신할 수 없다. 어떤 GNU 라이브러리를 사용하지 않는 비-유닉스 시스템들은 getenv 함수의 연속적인 호출에 의해서 덮어 쓰여질지 모른다(다른 라이브러리 함수에 의하지 않는다. ) 만일 환경변수 name이 정의되지 않는다면, 그 값은 널 포인터이다.

함수 : int putenv (const char *string)

putenv 함수는 환경으로부터 정의를 더하거나 제한다. 만일 string이 `name=value'의 형식이라면, 그 정의는 환경에 더해진다. 그렇지 않다면, 그 string은 환경변수의 이름으로써 해석되고, 환경 안에서 이 변수에 대한 정의가 제거된다. GNU 라이브러리는 SVID와의 호환성을 위해서 이 함수를 제공하고 있고; 다른 시스템에서 이 함수 는 유용하지 않다.
당신은 환경에 더 많은 변수들을 더하기 위해서 환경 오브젝트의 내재된 표현을 직접적으로 다룰 수 있다( 예로, 당신이 실행하려는 것에 대해서 다른 프로그램으로 통신하기 위해서; 23. 5절 [Executing a File] 참조).

변수 : chat **environ

환경은 문자열의 배열로써 표현된다. 각 문자열은 `name=value'의 형식을 갖는다. 환경 안에 나타난 문자열의 순서는 중요하지 않지만, 같은 이름이 한번 이상 나타나서는 결코 안된다. 배열의 마지막 요소는 널 포인터이다. 이 변수는 헤더파일 `unistd. h'에 선언되어 있다. 만일 당신이 환경변수의 값을 얻기 원한다면 getenv를 사용하라.

 

22. 2. 2 표준 환경 변수들

그들 환경변수들은 표준 의미를 갖는다. 이것은 그들이 항상 환경 안에서만 나타남을 의미하지는 않는다; 그러나 그 변수들이 어디에서든 나타난다고 해도 그들은 그들만의 의미를 갖기 때문에 당신이 다른 목적으로 환경변수 이름을 사용하려고 시도해서는 안된다.

HOME

이것은 사용자의 홈 디렉토리나, 초기 디폴트 작업 디렉토리를 나타내는 문자열이다. 사용자는 어떤 값으로 HOME를 설정할 수 있다. 만일 당신이 어떤 특정한 사용자를 위해서 적당한 홈 디렉토리를 부여하기를 원한다면, HOME을 사용할 수 없고; 대신에, 사용자 데이터 베이스에서 사용자의 이름을 찾아보라 ( 25. 12절 [User Database] 참조). 대부분의 목적에서는, HOME을 사용하는 것이 좋다, 왜냐하면 정확하게 사용자가 그 값을 정하도록 허용하기 때문이다.

LOGNAME

이것은 로그 인 하기 위해서 사용되는 사용자 이름이다. 환경에서 이 값은 제멋대로 조정될 수 있기 때문에, 이것은 프로세스를 실행시키고 있는 사용자를 구분하기 위한 신뢰 가능한 방법이 아니다; getlogin과 같은 함수(25. 11 [Who Logged In] 참조)는 그러한 목적을 위해서는 오히려 더 낫다. 대부분의 목적을 위해서는 LOGNAME이 사용자가 그 값을 지정하도록 하기 때문에 사용하기 더 좋다.

PATH

경로는 파일을 찾기 위해서 사용되는 디렉토리 이름들의 열(sequence) 이다. 변수 PATH는 실행시키려는 프로그램을 찾기 위해서 사용되는 경로는 저장하고 있다. execlp와 execvp 함수들은 (23. 5절 [Executing a File] 참조) 그들 함수들에 의하여 실행된 쉘과 다른 유틸리티에서, 이 환경변수를 사용한다. 경로의 구분은 콜론으로 분리된 디렉토리 이름들의 순차열(sequence) 이다. 디렉토리 이름 대신에 빈 문자열은 현재의 디렉토리를 나타낸다( 9. 1절 [Working Directory] 참조).
 
이 환경변수의 전형적인 값은 다음과 같은 문자열이 될 것이다.
: /bin: /etc: /usr/bin: /usr/new/X11: /usr/new: /usr/local/bin

TERM

이것은 프로그램 출력을 받아들이는 터미널의 종류를 지정한다. 어떤 프로그램들은 터미널의 특정한 종류에 의해 지원되는 특별한 이스케이프 시퀀스 또는 터미널모드들을 이용하기 위해 이 정보를 사용하게 만들 수 있다. termcap 라이브러리( Termcap 라이브러리 매뉴얼에서 "Finding a Terminal Description" 절을 참고)를 사용하는 많은 프로그램들은 TERM 환경변수를 사용한다.

TZ

이것은 시간대(time zone)를 지정한다. 이 문자열의 형식과 어떻게 그것을 사용하는지에 대한 자세한 정보는 17. 2. 5절 [TZ Variable] 참조.

LANG

속성 범주를 사용하도록 디폴트 지역을 지정한다.

LC_ALL

범주를 위한 환경변수가 아니면 설정한다. 지역에 대한 자세한 정보를 19장 [Locales] 참조.

LC_COLLATE

이것은 문자열 정렬을 위해서 어떤 지역을 지정한다.

LC_CTYPE

이것은 문자 집합(sets)과 문자 분류를 위해 사용하도록 어떤 지역을 지정한다.

LC_MONETARY

이것은 형식화된 통화량 값을 사용하기 위한 어떤 지역을 지정한다.

LC_NUMERIC

이것은 형식화된 숫자들을 사용하기 위한 어떤 지역을 지정한다.

LC_TIME

이것은 형식화된 날짜/시간값들을 위해서 어떤 지역을 지정한다.

_POSIX_OPTION_ORDER

만일 이 환경변수가 정의되었다면, 그것은 getopt에 의한 커맨드 라인 인수의 재정리를 금지한다. 22. 1. 1절 [Argument Syntax] 참조)


22. 3 프로그램 종료

프로그램을 종료시키는 평상적인 방법은 메인 함수가 반환하도록 하는 것이다. 메인 함수로부터 반환되는 분기(exit) 상황 값은 프로세스의 부모 프로세스나 쉘에게 정보를 보고하기 위해서 사용된다. 프로그램은 exit 함수를 호출함으로써 또한 종료할 수 있다. 또한, 프로그램은 시그널들에 의해서 종료될 수 있다; 이것은 21장 [Signal Handling] 에 자세하게 설명되어 있다. abort함수는 프로그램을 죽이는(kill) 시그널을 발생시킨다.

 

22. 3. 1 보통의 종료

프로세스는 프로그램이 exit를 호출할 때 보통 종료된다. main 함수로부터의 반환은 exit 를 호출하는것과 같고, main이 반환한 값은 exit에 인수로써 사용된다.

함수 : void exit (int status)

exit 함수는 상황 status로 프로세스를 종료한다. 이 함수는 반환하지 않는다.
보통의 종료는 다음과 같은 동작들의 원인이 된다:
 
1. atexit 나 on_exit 함수들에 등록된 함수들은 등록의 역순으로 호출되어진다. 이 메커니즘은 프로그램이 종료할 때 수행되어지는 자신만의 "cleanup" 동작들을 지정하도록 당신의 응용프로그램에게 허용한다. 전형적으로, 이것은 파일에 프로그램 상황 정보를 저장하거나, 공유 데이터베이스에서 락들을 해제하는것과 같은 일들을 하는데 사용된다.
 
2. 버퍼된 출력 데이터가 모두 기록되어, 개방된 모든 스트림이 폐쇄되었다. 7. 4절 [Closing Streams] 참조. 또한, tmpfile 함수에의해 개방되었던 임시 파일들은 제거된다; 9. 10절 [Temporary Files] 참조.
 
3. 프로그램을 종료시키도록 _exit가 호출되었다. 22. 3. 5절 [Termination Internals] 참조.

 

22. 3. 2 Exit 상황들

프로그램이 종료할 때, 그것은 분기 상황(exit status)을 사용해서, 종료의 원인에 대한 작은 양의 정보를 부모 프로세스에게 반환할 수 있다. 이것은 현존하는 프로세스가 exit에게 인수로써 주는 0에서 255사이의 값이다. 보통 당신은 성공이나 실패에 대한 매우 광범위한 정보를 보고하기 위해서 exit 상황을 사용할 것이다. 당신은 실패의 이유에 대해서 매우 자세하게는 정보를 줄 수는 없고, 대부분의 부모 프로세스도 더 이상 세밀한 정보를 원하지 않을 것이다.

어떤 프로그램이 반환하는 값은 어떤 부류의 상황 값에 속한다. 대부분의 관습은 성공하면 0이고 실패하면 1이다. 그러나 비교를 수행하는 프로그램은 다른 관습을 사용한다: 매치되지 않았음을 지적하기 위해서 1을 사용하고 비교 불가능임을 지적하기 위해서 2를 사용한다. 당신의 프로그램이 그것을 위한 종료관습을 이해하려면 종료관습을 따라야한다.

일반적인 관습은 상황 값으로 128개를 예약하고 특별한 목적을 위해서 확장한다. 특별하게, 128개의 값은 서브프로세스에서 다른 프로그램에게 실패를 지적하기 위해서 사용된다. 이 관습은 보편적으로 제공되지는 않지만, 당신의 프로그램이 그것을 따르도록 하는 것이 좋다.

 
주의 : 분기상황으로써 에러들의 개수를 사용하려고 시도하지 말아라.

이것은 실제로 매우 유용하지 않다; 부모 프로세스는 얼마나 많은 에러들이 발생했는지에 주의를 기울이지 않는다. 그것보다 더 심각한 경우로는, 상황 값이 8비트에서 짤리기 때문에 만일 프로그램에서 256개의 에러를 보고한다면, 부모 프로세스는 0개의 에러가 난 것으로 보고를 받을 것이고_결국 성공한 것으로 되어버린다. 같은 이유로, 255개를 초과할 수 있기 때문에 종료의 상황으로써 에러의 개수를 사용하여 작업하지 말아라.

 
이식성 노트: 어떤 비-POSIX 시스템들은 종료 상황 값들을 위해서 다른 관습을 사용한다. 좀더 좋은 이식성을 위해서는, 성공과 실패를 위한 상황 값으로 매크로 EXIT_SUCCESS 와 EXIT_FAILURE 를 사용할 수 있다. 그들은 헤더파일 `stdlib. h'에 선언되어 있다.

매크로 : int EXIT__SUCCESS

이 매크로는 성공적인 프로그램 수행을 알리기위해서 exit 함수와 함께 사용될 수 있다. POSIX 시스템에서, 이 매크로의 값은 0이고, 다른 시스템에서 이 값은 어떤 다른 정수 표현( 아마도 비-상수 )이 될 것이다.

매크로 : int EXIT__FAILURE

이 매크로는 비성공적인 프로그램 수행을 알리기위해서 exit 함수와 함께 사용될 수 있다. POSIX 시스템에서, 이 매크로의 값은 1이다. 다른 시스템들에서 그 값은 어떤 다른 정수 표현( 아마도 비-상수 )이 될 것이다. 다른 0이 아닌 상황 값들은 앞으로의 일을 알린다. 어떤 프로그램들은 특정한 종류의 "비-성공"을 알리기위해서 다른 0이 아닌 상황 값들을 사용한다. 예를 들어, diff는 파일들이 서로 다름을 의미할 때 상황 값 1을 사용하고, 파일을 개방하기가 어렵다면 2 또는 그 이상의 값을 사용한다.

 

22. 3. 3 종료시의 상황정리

당신의 프로그램에서 보통의 종료가 발생하면 그 자신만의 정리(cleanup) 함수를 실행하여 정돈할 수 있다. 만일 당신이 여러 개의 응용 프로그램에서 한 개의 라이브러리를 사용하고 있을 때, 모든 응용프로그램이 종료되기전에 라이브러리의 정리(cleanup) 함수들을 명시적으로 호출한다면 신뢰성이 없다. atexit 또는 on_exit를 사용해서 라이브러리 그 자체에서 정리 함수를 설정함으로써, 응용 프로그램에게는 보이지 않는 정리(cleanup)함수를 만드는 것이 더 신뢰성 있는 프로그램이 된다.

함수 : int atexit (void (*function) (void))

atexit 함수는 프로그램이 정상적으로 종료하면 호출되도록 함수 function을 등록한다. 그 function은 아무런 인수가 없이 호출된다. atexit로부터의 반환값은 성공하면 0이고 만일 그 함수가 등록될 수 없으면 0이 아닌 값을 반환한다.

함수 : int on__exit (void (*function)(int status, void *arg), void *arg)

이 함수는 atexit 보다는 좀더 다양함을 구사할 수 있는 것이다. 이 함수는 두 개의 인수로써 함수 function과 포인터 arg를 받아들인다. 정상적이 프로그램 종료시하다, 그 function은 두 개의 인수들, 즉 exit에 주었던 상황 값과 arg와 함께 호출된다. 이 함수는 SunOS와의 호환성을 위해 GNU C 라이브러리에 포함되었으며, 다른 시스템에서는 지원되지 않을 것이다.
 
다음은 exit 와 atexit 의사용을 설명하는 프로그램 예제이다.
 
#include <stdio. h>
#include <stdlib. h>
 
void bye (void)
{
puts ("Goodbye, cruel world. . . . ");
}
 
int main (void)
{
atexit (bye);
exit (EXIT_SUCCESS);
}
 
이 프로그램이 실행됐을 때, 단지 메시지를 프린트하고 종료한다.

 

22. 3. 4 프로그램 중지시키기

당신은 abort 함수를 사용하여 당신의 프로그램을 중지시킬 수 있다. 이 함수를 위한 프로토타입은 `stdlib. h'에 있다.

함수 : void abort (void)

abort 함수는 비정상적으로 프로그램을 종료시킨다. 이것은 atexit 또는 on_exit로 등록된 정리(cleanup)함수들을 실행하지 않는다. 이 함수는 SIGABRT 시그널을 발생시킴으로써 프로세스를 종료시키고, 당신의 프로그램은 이 시그널을 가로채서 처리하는 핸들러를 포함할 수 있다.

 

22. 3. 5 내부적 종료

_exit 함수는 exit에 의해서 프로세스를 종료하기 위해서 사용되는 기본 동작이다. 이것은 `unistd. h'에 선언되어 있다.

함수 : void __exit (int status)

_exit 함수는 상황 status로 프로세스가 종료되도록 하는 기본 동작(primitives)이다. 이 함수를 호출하는 것은 atexit 또는 on_exit에 등록된 정리(cleanup) 함수들이 실행되지 않는다.

명백히 종료를 호출하거나, 또는 시그널의 결과에 의한 종료이거나_어떤 이유에 의해서 프로세스가 종료될 때, 다음과 같은 일이 발생한다.

프로세스에서 모든 개방된 파일 기술자들은 폐쇄된다. 8장 [Low-Level I/O] 참조.

종료 상황 코드의 하위 8비트는 wait 나 waitpid를거쳐서 부모 프로세스에게 보고되도록 저장된다; 23. 6절 [Process Completion] 참조.

종료된 프로세스의 어느 자식 프로세스들은 새로운 부모 프로세스가 할당된다. (이것은 프로세스 ID 1로서, 처음의 프로세스이다. )

SIGCHLD 시그널은 부로 프로세스에게 보내어진다.

만일 그 프로세스가 제어중인 터미널을 가진 세션 리더(session leader)라면, SIGHUP 시그널은 전면 작업에 있는 각각의 프로세스에게 보내어 지고, 제어중인 터미널은 세션으로부터 분열된다. 24장 [Job Control] 참조.

어떤 프로세스의 종료가 프로세스 그룹을 고아가 되도록 하고, 프로세스 그룹의 어떤 멤버를 멈추도록 하는 원인이 된다면, SIGHUP 시그널과 SIGCONT 시그널이 그룹에 있는 각각의 프로세스에게 보내어진다. 24장 [Job Control] 참조.


목차 이전 : 21. 시그널 처리 다음 : 23. 프로세스

'Programming > Unix Programing' 카테고리의 다른 글

24. Job Control(작업 제어)  (0) 2007.12.22
23. Processes  (0) 2007.12.22
22. 프로세스의 시동과 종료(Process StartUp)  (0) 2007.12.22
21. 신호처리(Signal)  (0) 2007.12.22
19. 지역과 세계화  (0) 2007.12.22
18. 확장된 문자  (0) 2007.12.22
Comment 0 Trackback 0
Top

prev 1 2 next