태터데스크 관리자

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

태터데스크 메시지

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

haRu™'s Thinks

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


'Unix Programming'에 해당되는 글 32건

  1. 2007.12.22 22. 프로세스의 시동과 종료(Process StartUp)
  2. 2007.12.22 21. 신호처리(Signal)
  3. 2007.12.22 19. 지역과 세계화
  4. 2007.12.22 18. 확장된 문자
  5. 2007.12.22 17. 날짜와 시간
  6. 2007.12.22 16. 형식일치( Pattern Matching )
  7. 2007.12.22 15. 탐색과 정렬
  8. 2007.12.22 14. 저수준 연산 함수들
  9. 2007.12.22 13. 수학 함수
  10. 2007.12.22 12. Low-Level Terminal Interface

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

21. 신호처리(Signal)

목차 이전 : 20. 비-지역 탈출 다음 : 22. 프로세스의 시동과 종료


21 시그널 처리

시그널(앞으로 신호라 해석하지 않고 시그널이라고 하겠습니다. 그것이 더 좋을 것 같아서. . . )은 프로세스에게 배달된 소프트웨어 인터럽트이다. 운영체제는 실행하고 있는 프로그램에 예외적인 상황을 보고하기 위해서 시그널을 사용한다. 어떤 시그널들은 유용하지 않은 메모리 주소를 참조하는것과 같은 에러를 보고하고; 다른 것은 전화선의 단절과 같은, 비동기적 사건을 보고한다.

GNU C 라이브러리는 각각의 특정한 사건들의 종류에 따라, 다양한 시그널의 형태를 정의한다. 사건들의 어떤 종류들은 보통 프로그램의 계속된 진행을권장할 수 없거나 불가능하게 하고, 그에 해당하는 시그널들은 보통 그 프로그램을 중지시킨다. , 유해하지 않은 사건들을 보고한 다른 종류의 시그널들은 보통 무시된다.

만일 당신이 시그널이 발생한 사건을 예상한다면, 당신은 시그널 처리 함수를 정의할 수 있고 특정한 형태의 시그널이 도착했을 때 운영체제가 그 시그널 처리함수를 실행하게 할 수 있다. 최종적으로, 하나의 프로세스는 다른 프로세스에게 한 개의 신호를 보낸다; 이것은 부모 프로세스가 자식 프로세스를 중지시키는 것을 허용하거나, 또는 두 개의 연관된 프로세스가 통신하거나 동기하도록 하는 것을 허용한다.


21. 1 시그널들의 기본 원칙

어떻게 시그널들이 발생되고, 시그널이 도착된 이후에 무슨 일이 발생할 것이며, 어떻게 프로그램이 시그널을 처리할 수 있는지에 대한 기본 원칙들을 설명한다.

 

21. 1. 1 시그널들의 종류

신호는 예외적인 사건의 발생을 보고한다. 다음은 시그널을 발생시키는 어떤 예외적인 사건들이다.

프로그램이 0으로 나누는 일을 하거나, 또는 유용한 범위를 넘어서는 주소를 억세스하려는것과 같은 에러.

사용자가 프로그램을 인터럽트 또는 중지하도록 요청한다. 대부분의 환경들은 사용자가 C-z를 타이핑하면 일시 중지하거나, C-c를 타이핑하면 종료를 허용하도록 만들어졌다. 키 시퀀스( key sequence)에 무엇이 사용되었던지, 운영체제는 프로세스를 인터럽트 하기 위하여 적당한 시그널을 보낸다.

자식 프로세스의 종료.
타이머나 알람의 경과.
같은 프로세스에 의해 죽이거나 발생한 호출.
다른 프로세스로 부터 죽이기 위한 호출. 시그널들은 프로세스사이의 통신에 유용한 형식이지만 제한을 갖는다.

이들 사건들(죽이거나 발생하기 위해 명백하게 호출한 것을 제외하고 )의 각각은 자신만의 특정한 종류의 신호를 발생시킨다. 다양한 종류의 시그널들은 21. 2절 [Standard Signals] 에 상세하게 설명되었다.

 

21. 1. 2 시그널 발생의 원칙들

일반적으로, 시그널을 발생시키는 사건들은 세 가지로 나눌 수 있다: 에러들. 외부의 사건들과 명백한 요청.

에러는 프로그램이 무언가 유용하지 않을 일을 하고 실행을 계속할 수 없는 것을 의미한다. 그러나 에러들의 모든 종류가 시그널을 발생시키지는 않는다_실제로 그들의 대부분은 시그널을 발생시키지만. . . 예를 들어, 존재하지않는 파일을 개방하기와 같은 것은 에러이지만, 그것은 시그널을 발생시키지 않고; 대신에 open은 -1을 반환한다. 일반적으로, 에러들은 에러를 지적하는 값을 반환함으로써 보고되는 어떤 라이브러리 함수들과 연관되어있다. 시그널들을 발생시킨 에러들은 단지 라이브러리 호출뿐만 아니라 프로그램의 어디서든 발생할 수 있다. 그들에는 0으로 나누기를 하고 유용하지 않은 메모리 주소의 참조가 포함된다.

외부에서 발생한 사건은 입/출력이나 다른 프로세스들과 함께 하는 것에서 나온다. 그들에는 입력의 도착, 타이머의 경과, 자식 프로세스의 종료등이 포함된다.

명백한 요청은 kill처럼 특별하게 시그널을 발생하도록 어떤 목적을 가진 라이브러리 함수의 사용을 의미한다.

시그널들은 동기적으로 또는 비동기적으로 발생되어진다. 동기적 시그널 은 프로그램의 어느 정해진 동작과 관계하고, 그 동작을 하는 동안(블록된 것이 아니라면)에 배달 되어진다. . 에러들은 동기적으로 시그널을 발생하고, 프로세스가 같은 프로세스를 위하여 시그널을 발생하도록 함으로써 명백하게 요청한다.

비동기적 시그널들은 시그널을 받은 프로세스의 제어의 밖에서 발생한 사건에 의해 발생되어진다. 그들 시그널들은 실행동안에 예측할 수 없는 시간에 도착된다. 외부의 사건들은 비동기적으로 시그널들을 발생하고, 어떤 다른 프로세스에 적용하도록 명백하게 요청한다.

시그널의 주어진 형태는 전형적으로 동기적이거나 또는 비동기적중에 하나가 된다. 예를 들어, 에러를 위한 시그널은 에러가 동기적으로 신호를 발생했다면 전형적으로 동기적이다. 그러나 어느 시그널의 형태는 명백한 요청으로는 동기적, 또는 비동기적으로 발생되어질 수 있다.

 

21. 1. 3 어떻게 신호들이 배달되는가

시그널이 발생되어졌을 때, 그때는 아직 미해결인 상태가 된다. 일반적으로 아주 짧은 시간동안만 미해결인채로 남아있고 그 다음에는 신호가 프로세스에게 배달 되어진다. 그렇지만 만일 시그널의 종류가 블록되어졌다면, 그것은 막연히 미해결인체로 남아있게 될 것이다_그 시그널의 블록이 해제될때까지. 일단 블록이 해제되면, 그것은 즉시 배달되어질 것이다. 21. 7절 [Blocking Signals] 참조.

시그널이 배달되었을 때, 즉시 또는 긴 지연후에 그 시그널을 위하여 정해진 행동을 하게 된다. SIGKILL 과 SIGSTOP 와 같은 어떤 시그널들은 그 행동이 정해져있지만, 대부분의 시그널들은 프로그램이 선택하게 된다: 시그널을 무시하거나, 처리 함수를 지정하거나, 또는 시그널의 종류 에 따라 디폴트 동작을 하거나. 프로그램은 signal이나 sigaction과 같은 함수들을 사용해서 선택을 하게 된다(21. 3절 [Signal Actions] 참조. ).

우리는 때때로 핸들러가 시그널을 잡았다고 얘기를 한다. 핸들러가 실행되고 있는 동안, 특정한 시그널은 일반적으로 블록되어진다. 만일 한 종류의 시그널을 위한 정해진 동작이 시그널을 무시하는것이라면, 발생되어진 시그널은 즉시 버려진다 이것은 심지어 시그널이 동시에 블록 되어질지라도 발생한다. 이렇게 버려진 시그널은 비록 프로그램이 연속적으로 그 종류의 시그널을 위하여 다른 동작을 지정하고 블록을 해제하지 않았을지라도 결코 배달되지 않을 것이다.

프로그램에서 처리되지도 않고 무시하지도 않는 신호가 발생하면, 그것의

디폴트 동작이 일어난다. 시그널의 각 종류들은 밑에 설명된 자신의 디폴트 동작을 갖는다(21. 2절 [Standard Signals] 참조. ). 대부분의 시그널에서 디폴트 동작은 프로세스를 종료하는 것이다. "유해하지 않은" 사건들에서 발생한 어떤 종류의 시그널들의 디폴트 동작은 아무 것도 하지않는 것이다.

한 시그널이 프로세스를 종료할 때, 그것의 부모 프로세스는 wait 또는 waitpid 함수들에 의해 보고된 종료 상황 코드를 조사함으로써 종료가 발생한 원인을 알아낼 수 있다. (이것에 대해서는 23. 6절 [Process Completion] 에 좀더 자세하게 나와있다. ) 시그널의 원인이 된 종료의 요소, 그리고 시그널의 종료를 포함해서 정보를 얻을 수 있다. 만일 쉘로부터 당신이 실행하고 있는 어떤 프로그램이 시그널에 의해 종료가 된다면, 그 쉘은 그것에 해당하는 에러메세지를 프린트 할 것이다. 프로그램의 에러를 표현하는 시그널들은 특별한 속성을 갖는다: 시그널 중의 하나가 프로세스를 종료할 때, 종료한 시간에 프로세스의 상황에 대한 기록을 코어 덤프 파일에 기록한다. 당신은 무슨 에러가 발생했는지 조사 하기 위해서 디버거를 사용해서 코어 덤프 파일을 조사할 수 있다.

만일 당신의 명백한 요청에 의해서 프로세스를 종료하고 "프로그램 에러" 시그널이 발생하면, 직접적으로 에러에 기인하는 시그널로써 코어 덤프 파일을 만든다.


21. 2 표준 시그널들

이 절은 다양한 표준 시그널들의 이름과 그것이 어떤 사건을 의미하고 있는지를 설명하고 있다. 각각의 시그널의 이름은 시그널의 종류를 위한 시그널번호로써 양의 정수로 나타낸 매크로이다. 당신은 여기에서 정의한 이름대신에 당신 마음대로 시그널의 번호코드를 결코 가정할 수 없다. 이것은 시그널의 종류에 부여된 번호가 시스템에서 시스템으로는 바꿀수 있지만, 그 이름들의 의미는 표준화되어있고 완전히 단일화 되어있기 때문이다.

시그널의 이름들은 헤더파일 `signal. h'에 정의되어 있다.

매크로 : int NSIG

이 심볼 상수의 값은 정의된 시그널의 총 개수이다. 시그널의 번호들은 연속적으로 할당되어 있기 때문에 NSIG는 정의된 시그널의 번호중에서 가장 큰 번호보다 하나가 크다.

 

21. 2. 1 프로그램 에러 시그널들

다음의 시그널들은 심각한 프로그램의 에러가 운영체제나 컴퓨터 자체에 의해 검출되었을 때 발생 된다. 일반적으로, 이들 시그널 모두는 당신의 프로그램이 심각하게 깨져있고, 에러가 포함된 그 실행을 계속할 아무런 방법이 없음을 지적한다.

어떤 프로그램들은 프로그램의 에러 시그널로 인해서 종료되기전에 그들을 깨끗하게 처리한다. 예를 들어, 터미널 입력의 반향을 끈(tnun off) 프로그램들은 다시 반향을 켤 목적으로 프로그램 에러 시그널들을 처리할 것이다. 핸들러는 시그널을 위한 디폴트 동작을 정하고 그 동작을 함으로써 끝날 것이다; 만일 프로그램이 시그널 핸들러를 가지지 않았다면, 프로그램은 그 시그널로 인해서 종료될 것이다. ( 21. 4. 2절 [Termination in Handler] 참조. )

종료는 대부분의 프로그램에 에서 에러에 대응한 이해 가능한 최종적인 결과이다. 그렇지만, Lisp과 같은 프로그래밍시스템들은 사용자 프로그램에 에러가 발생했을지라도 컴파일된 사용자 프로그램을 실행시켜야할 필요가 있다면 로드(load)시킬 수 있다. 이들 프로그램은 커멘드 레벨(command level)로 제어를 반환하는 longjmp를 사용한 핸들러를 갖는다. 모든 시그널의 디폴트 동작은 프로세스를 종료하는 것이다. 만일 당신이 그 시그널들을 블록하거나 무시하거나 시그널을 위한 핸들러를 만든다면, 당신의 프로그램은 아마도 그와같은 시그널들이 발생했을 때, 그들이 실제 에러대신에 raise나 kill에 의해 발생된 것이 아니라면, 심각하게 파괴될 것이다.

그들 프로그램 에러 시그널중의 하나가 프로세스를 종료할 때, 종료와 같은 시간에 프로세스의 상황기록을 코어덤프 파일에 출력한다. 코어덤프 파일은 `core'라고 이름지어졌고 프로세스가 현재 존재하고 있는 디렉토리 에 존재한다. ( GNU 시스템에서, 당신은 환경변수 COREFILE를 통해서 코어 파일의 이름을 지정할 수 있다. ) 코아덤프파일의 존재 목적은 무슨 에러가 발생했는지 조사 하기 위함으로써, 디버거를 사용해서 그들을 시험할 수 있다.

매크로 : int SIGFPE

SIGFPE 시그널은 심각한 산술적 에러를 보고한다. 그 이름이 "floating-point exception"에서 유래된것이라 할지라도, 이 시그널은 실제로는 모든 산술적 에러들에 작용한다. 만일 어떤 프로그램이 어떤 위치에 정수 데이터를 저장하고 그 데이터에 플로팅-포인트 명령을 사용한다면, 이것은 그 프로세서가 데이터를 플로팅-포인트 수로써 인식할 수 없기 때문에 종종 "유용하지 않은 연산"의 원인이 된다.

플로팅-포인트 예외상황에 대한 것은 아주 민감하게 다른 의미를 지닌 예외상황의 여러종류들이 있기 때문에 아주 복잡한 주제이고, SIGFPE 시그널은 그들을 구분하지 않는다. 이진 플로팅-포인트 연산을 위한 IEEE 표준(ANSI/IEEE Std 754-1985)은 다양한 플로팅-포인트 예외상황에 대해서 정의하고 있고 컴퓨터 시스템이 예외상황의 발생을 보고할 때 따르도록 요구한다. 그렇지만, 이 표준은 그 예외상황이 어떻게 보고되는 지에 대해서는 지정하지 않았고, 또한 운영체제가 제어와 처리의 어떤 종류를 프로그래머에게 제공할 수 있는지를 지정하지 않았다.

BSD 시스템들은 예외상황의 다양한 원인을 구별하는 특별한 인수를 가진 SIGFPE 핸들러를 제공한다. 이 인수를 억세스 하기 위해서, 당신은 두 개의 인수를 받아들이는 핸들러를 정의해야만 한다. GNU 라이브러리는 이 특별한 인수를 제공하지만, 그 값은 BSD 시스템과 GNU 시스템에서만 오직 의미가 있다.

 
역자주 : trap(트랩) : 하나의 명령어가 실행될 따마다 자동적으로 발생되는 인터럽트. 이러한 인터럽트는 중앙처리 장치에 의하여 하드웨어 적으로 발생하게 되는데 프로그램에서 하나의 명령어가 실행될 때마다 자동적으로 미리 정의된 트랩 처리 루틴으로 실행의 제어권이 넘어온다. 하드웨어 장치와 밀접한 관련이 있는 시스템 소프트웨어에서 오류를 찾아내기 위한 수단으로 이용된다.

FPE_INTOVF_TRAP

정수 오버플로우 ( 당신이 하드웨어의 정해진 사양에 따라서 오버플로우의 트랩이 가능하지 않으면 C 프로그램에서는 불가능하다. )

FPE_INTDIV_TRAP

정수를 0으로 나누기.

FPE_SUBRNG_TRAP

아래에 기입한-범위 ( 어떤 C 프로그램은 결코 체크하지 않는다. )

FPE_FLTOVF_TRAP

플로팅 오버플로우 트랩.

FPE_FLTDIV_TRAP

플로팅/정수를 0으로 나눔.

FPE_FLTUND_TRAP

플로팅 언더플로우. 트랩 ( 플로팅 포인트에서 트랩 하는 것은 보통 가능하지 않다. )

FPE_DECOVF_TRAP

십진수 오버플로우 트랩. ( 오직 몇 개의 기계에서만 십진수 연산을 갖고 있고 C에서는 결코 그것을 사용하지 않는다. )

매크로 : int SIGILL

이 시그널의 이름은 "illegal instruction : 비합법적인 명령"에서 유래되었다; 그것은 쓸모없거나 특권이 부여된 명령어를 실행하려 했다는 의미이다. 오직 유용한 명령어만이 발생된 C 컴파일러에서, SIGILL은 전형적으로 실행 가능 파일이 훼손되었거나, 당신이 데이터를 실행하려 시도했다는 것을 지적한다. 후자의 상황이 발생되는 일반적 상황으로는 함수를 위한 포인터가 있을 것이라고 예상된 곳에서 유용하지 않은 오브젝트를 파싱하거나, 자동 배열의 끝을 넘어서 기록을 하고( 또는 자동 변수를 위한 포인터와 유사한 문제들) 스택 프레임의 반환 어드레스 처럼 스택에서 다른 데이터의 훼손과 같은 문제들이 있다.

매크로 : int SIGSEGV

이 시그널은 할당된 메모리의 범위를 벗어나는곳에서 읽거나, 쓰기를 시도할 때 발생 된다. ( 실제로, 그 시그널들은 프로그램이 충분한 영역을 할당받지 못할 때 시스템 메모리 보호 메커니즘에 의해서 발생한다. ) 그 이름은 "segmentation violation"의 약자이다. SIGSEGV 상황이 발생되는 가장 일반적인 방법은 비참조 되는 널( defeferencing a null) 이나 초기화되지 않은 포인터에 의한 것이다. 널 포인터는 주소 0으로 참조되고, 대부분의 운영체제는 이 주소가 정확하게 유용하지 않음을 확실히 하기 때문에 비참조 널 포인터는 SIGSEGV가 발생될 것이다. (어떤 운영체제는 주소가 0인 메모리도 유용하고, 비참조 널 포인터는 그들 시스템상에서는 시그널을 발생하지 않는다. ) 비초기화된 포인터에서는, 유용하지 않거나, 유용하더라도 임의의 주소들을 갖게된다. SIGSEGV 상황이 얻어지는 다른 일반적 방법은 배열에 포인터를 사용했을 때 그 배열의 끝을 체크하기를 실패했을 때이다.

매크로 : int SIGBUS

이 시그널은 유용하지 않은 포인터가 비참조되었을 때 발생 된다. SIGSEGV 처럼, 이 시그널은 초기화되지 않은 포인터를 비참조 한 것의 결과이다. 두 시그널의 차이점은 SIGSEGV는 유용한 메모리에서 유용하지못한 억세스를 지적하고, SIGBUS는 유용하지못한 주소를 억세스 하는 것을 지적한다. 특별하게, SIGBUS 시그널은 4개로 나누어지지 않은 주소에 4-단어 정수로 참조하는것처럼, 부적당한 포인터가 비참조 됨으로써 발생한다. (각종 시스템은 주소 정렬은 위한 자신만의 필요조건을 갖는다. ) 이 시그널의 이름은 "bus error"의 약자이다.

매크로 : int SIGABRT

이 시그널은 프로그램 그 자체와 abort가 호출되었음을 보고함으로써 발생되는 에러를 지적한다. 22. 3. 4절 [Aborting a Program] 참조.

 

21. 2. 2 종료 시그널

이들 시그널들은 이런 저런 방법으로 프로세스를 종료함을 알리기위해 사용된다. 그들은 완전히 다른 목적을 위해 사용되기 때문에 다른 이름을 가졌고, 프로그램은 그들은 다르게 취급하기를 원할 것이다.

이들 시그널들은 처리하기 위한 이유는 보통 당신의 프로그램이 실제로 종료되기전에 적당하게 처리할 수 있도록 하기 위한 것이다. 예를 들어, 당신은 상황정보를 저장하고, 임시 파일들을 지우고, 이전의 터미널 모드를 반환하기를 원할수도 있다. 그와 같이 핸들러(handler)는 발생된 시그널을 위한 디폴트 동작을 지정하고 그리고 그 시그널을 다시 발생시킴으로써 종료할 것이다. 이것은 만일 프로그램이 핸들러를 가지지 않았더라도, 그 시그널로 인해서 프로그램이 종료될 것이다. ( 21. 4. 2절 [Termination in Handler] 참조. )

이 시그널들을 위한 (명백한) 디폴트 동작은 프로세스가 종료되도록 하는 것이다.

매크로 : int SIGHUP

SIGHUP ("hang-up") 시그널은 사용자 터미널의 단절을 보고하기 위해 사용되어지는데, 아마도 네트웍이나 전화선 연결이 끊어졌기 때문이다. 이것에 대한 상세한 정보는 12. 4. 6절 [Control Modes] 참조. 이 시그널은 또한 그 세션과 연관된 작업을 위해서 터미널에서 제어하고 있는 프로세스의 종료를 보고하기 위해 사용되어진다; 이 종료는 제어중인 터미널로부터 그 세션안에 있는 모든 프로세스를 효과적으로 단절한다. 더 상세한 정보는 22. 3. 5절 [Termination Internals] 참조.

매크로 : int SIGINT

SIGINT("program interrupt") 시그널은 사용자가 INTR 문자를 (보통 C-c)을 입력했을 때 보내어진다. 터미널 드라이버가 C-c 를 지원하는지에 대한 정보는 12. 4. 9절 [Special Characters] 참조.

매크로 : int SIGQUIT

SIGQUIT 시그널은 다른 키_QUIT 문자, 보통 C-\_에 의해서 제어된다는 것을 제외하고는 SIGINT와 유사하고, 그 프로세스가 종료 될 때 프로그램 에러 시그널처럼 코어 파일을 작성한다. 당신은 사용자에 의해 "검출된" 프로그램 에러 상황으로 이들을 생각할 수 있다. 코어 덤프 파일에 대한 정보는 21. 2. 1절 [Program Erroe Signals] 참조. 지원하는 터미널 드라이버에 대한 정보는 12. 4. 9절 [Special Characters] 참조. 소거의 어떤 종류들은 SIGQUIT를 처리하는 동안에 생략되어지는 것이 좋다. 예를 들어, 만일 프로그램이 임시파일을 만든다면, 그것은 임시파일을 지움으로써 다른 종료 요청을 처리할 것이다. 하지만 사용자가 코어 덤프 파일을 시험할수 있게 하기 위하여, 그들을 지우지 않는 것이 SIGQUIT를 위해서 더 좋다.

매크로 : int SIGTERM

SIGTERM 시그널은 프로그램을 종료하는데 사용하는 포괄적인 시그널이다. SIGKILL과 달리, 이 신호는 블록되어진고, 처리되어지고 무시되어질 수 있다. 쉘 코맨드 kill은 디폴트로 SIGTERM을 발생시킨다.

매크로 : int SIGKILL

SIGKILL 시그널은 즉각적인 프로그램 종료를 일으키기 위해서 사용되어진다. 이 시그널은 처리되거나, 무시되거나 할 수 없고, 그 결과는 항상 치명적이 된다. 이 시그널은 블록하는것도 불가능하다.
 
이 시그널은 오직 명백한 요청에의해 발생되어진다. 그것이 처리되어질 수 없다면, 당신은 일단C-c 또는 SIGTERM과 같은 덜 격렬한 방법을 시도한 후에, 나중에 마지막 방법으로 오직 그것을 발생시킬 것이다. 만일 프로세스가 어느 다른 종료 시그널들에 반응하지 않는다면, SIGKILL시그널을 보내면 거의 항상 그 프로세스가 종료될 것이다. 실제로, SIGKILL이 프로세스를 종료하는데 실패한다면, 운영체제의 버그 때문이다.

 

12. 2. 3 알람 시그널

그들 시그널은 타이머의 경과를 지적하는데 사용되어진다. 이들 시그널을 보내는 함수에 대한 정보는 17. 3절 [Settin an Alarm] 참조. 그들 시그널을 위한 디폴트 동작은 프로그램을 종료를 일으키는 것이다. 이 디폴트 동작은 거의 유용하지 않다; 그들 시그널을 사용하는 대부분의 방법은 어느 경우에 맞는 핸들러 함수들을 요구하는 것이다.

매크로 : int SIGALRM

이 시그널은 전형적으로 실제또는 클럭 시간을 계산한 타이머의 경과를 지적한다. 예를 들어 alarm 함수에의해 사용되어진다.

매크로 : int SIGVTALRM

이 시그널은 전형적으로 현재 프로세스에 의해 사용된 CPU시간을 계산하는 타이머의 경과를 지적한다. 그 이름은 "virtual time alarm"의 약자이다.

매크로 : int SIGPROF

이 시그널은 현재의 프로세스에 의해 사용된 CPU 시간과, 프로세스를 대신하여 시스템에의해 사용된 CPU시간의 둘을 계산한 타이머의 경과를 지적하는데 사용된다. 타이머가 자원의 프로파일링을 위한 도구로써 사용되어지므로, 시그널의 이름이 SIGPROF이다.
역자주 : profiling: 프로파일링 : 시스템의 성능 및 병목현상을 방지하기 위한 도구라고 생각하시면 될 것 같네요. 정확하지가 않아서. .

 

21. 2. 4 비동기 입/출력 시그널

이 절에 설명된 시그널들은 비동기 입/출력 도구들과 함께 사용되어진다. 당신은 어떤 특정한 파일 기술자가 그들 시그널을 발생시키도록 하기 위해서 fcntl을 호출함으로써 명백한 동작을 취하도록 해야한다( 8. 12절[Interrupt Input] 참조. ) 그들 시그널을 위한 디폴트 동작은 그들을 무시하는 것이다.

매크로 : int SIGIO

이 시그널은 파일기술자가 입력 또는 출력을 수행할 준비가 되어있을 때 보내어진다. 대부분의 운영체제에서, 터미널과 소켓만이 SIGIO를 발생시킬 수 있다; 보통의 파일들을 포함한 다른 종류들은 당신이 그들에게 요청했을지라도 SIGIO신호를 발생시키지 않는다.

매크로 : int SIGURG

이 시그널은 소켓에 도착한 데이터가 "긴급"하거나 범위를 벗어 났을 때 보내어진다. 11. 8. 8절 [Out-of-Band Date] 참조.

 

21. 2. 5 작업 제어 시그널

이들 시그널은 작업 제어를 지원하기 위해서 사용되어진다. 만일 당신의 시스템이 작업 제어를 지원하지 않는다면 시그널들은 발생되어지거나, 처리될 수는 없지만 매크로들은 정의되어있다. 당신이 실제로 작업이 어떻게 제어되는지를 이해할 수 없다면 그들 시그널을 그대로 방치할 것이다. 24장 [Job Control] 참조.

매크로 : int SIGCHLD

이 시그널은 자식 프로세스들중의 하나라도 종료되거나 멈출 때마다 부모 프로세스에게 보내어진다. 이 시그널을 위한 디폴트 동작은 그것을 무시하는 것이다. 만일 당신이 wait 또는 waitpid를거쳐 (23. 6절 [Process Completion] 참조. ) 그들의 상황이 보고되지 않았지만, 종료된 자식 프로세스에서 발생한 시그널을 위한 핸들러를 만든다면, 당신의 새로운 핸들러가 그들 프로세스에 적용이 되던지 또는 특정한 운영체제에 달려있다.

매크로 : int SIGCONT

당신은 프로세스가 계속되도록 하기 위해서 SIGCONT 신호를 보낼 것이다. 이 시그널을 위한 디폴트 동작은 만일 그 프로세스가 멈추었다면 그 프로세스를 계속하도록 만드는 것이고 그렇지 않다면 그것을 무시하는 것이다. 대부분의 프로그램에서는 SIGCONT를 처리할 아무런 이유가 없다; 그들은 전에 멈추었었음을 인식함이 없이 계속 실행되고 있다고 가정한다.
 
당신은 어떤 특정한 동작을 하는 프로그램을 멈추거나 계속하도록 만들기 위해서 SIGCONT 시그널을 위한 핸들러를 사용할 수 있다_예를 들어, 입력을 기다리기 위해서 잠시 멈추었을 때 프롬프트를 다시 프린트 하는것과 같은.

매크로 : int SIGSTOP

SIGSTOP 시그널은 프로세스를 멈춘다. 그것은 처리되거나, 무시되거나 블록될 수 없다.

매크로 : int SIGTSTP

SIGTSTP 시그널은 상호 작용하는 멈춤 신호이다. SIGSTOP와는 달리 이 신호는 처리되거나 무시되어질 수 있다. 당신의 프로그램에서 프로세스가 멈추었을 때 파일이나 시스템 테이블을 안전한 상황으로 만들어놓을 특별한 필요가 있다면 이 신호를 처리할 수 있다.
 
예를 들어, 반향이 꺼진 프로그램에서는 멈추기 전에 다시 반향을 켜도록 SIGTSTP 시그널을 처리할 것이다. 이 시그널은 사용자가 SUSP 문자(보통 C-z)를 입력하다 때 발생 된다. 지원하는 터미널 드라이버에 대한 자세한 정보는 12. 4. 9절 [Special Characters] 참조.

매크로 : int SIGTTIN

한 프로세스가 배경 작업으로써 실행되고 있는 동안 사용자의 터미널로부터 읽을 수 없다. 배경 작업에 속한 어느 프로세스가 터미널로부터 읽으려 시도할 때, 그 작업에 속한 모든 프로세스는 SIGTTIN 신호를 받는다. 이 시그널을 위한 디폴트 동작은 그 프로세스를 멈추는 것이다. 어떻게 터미널 드라이버와 상호작용 하는지에 대한 자세한 정보는 24. 4절 [Access to the Terminal] 참조.

매크로 : int SIGTTOU

SIGTTIN과 유사하지만, 배경 작업에 속한 프로세스가 터미널에 출력하려 시도하거나 그 터미널 모드를 설정하려 시도할 때 발생 된다. 다시 말하면 디폴트 동작은 그 프로세스를 멈추는 것이다.
프로세스가 멈추어있을 동안, SIGKILL 시그널과 SIGCONT시그널을 제외하고는 어느 다른 시그널들은 배달되어질 수 없다.
 
SIGKILL 시그널은 항상 프로세스의 종료를 유발하고 블록되거나 무시될 수 없다. 당신이 SIGCONT 시그널을 무시하거나 블록할 수 있지만, 그것은 만일 그 프로세스가 멈추어져있다면 프로세스가 계속되도록 한다. 프로세스에게 보낸 SIGCONT 시그널은 아직 미해결인채로 남아있는 멈춤 시그널을 프로세스가 버리도록 한다. 이와 비슷하게, 어떤 프로세스에서 아직 미해결인채로 남아있는 SIGCONT 시그널은 멈춤 시그널이 도착했을 때 버려진다.
 
고아가 되어버린 프로세스 그룹에 있는 한 프로세스에게 SIGTSTP, SIGTTIN, 또는 SIGTTOU 시그널을 보내면 그것은 처리되지도 않고, 그 프로세스는 멈추어 지지도 않는다. 그것을 계속할 아무런 방법이 없는 부당하게 되어버린 프로세스를 멈추게 하라. 운영체제에 의존하지 말고당신이 무언가를 사용해서 멈추게 하라. 어떤 시스템은 아무런 일도 하지 않을 것이다. 다른 시스템들은 대신에 SIGKILL 또는 SIGHUP와 같은 시그널들을 배달할 것이다.

 

21. 2. 6 잡다한 시그널

그들 시그널은 다양한 다른 상활들을 보고하기 위해서 사용되어진다. 이들의 디폴트 동작은 프로세스가 종료되도록 하는 것이다.

매크로 : int SIGPIPE

만일 당신이 파이프나 FIFO들을 사용한다면, 당신의 어플리케이션에서 다른 것이 출력을 시작하기 전에 한 프로세스가 읽기를 위해서 파이프를 개방하도록 만들어야 한다. 만일 읽기 프로세스가 결코 시작되지 않거나, 급작스럽게 종료된다면 파이프나 FIFO에 출력하기는 SIGPIPE 시그널을 발생시킨다.
 
만일 SIGPIPE 가 블록되거나, 처리되어지거나, 무시되어지면, 그 손상된 호출은 대신에 EPIPE로 실패한다. 파이프와 FIFO 특별한 파일들은 10장 [Pipes and FIFOs] 에서 좀더 자세하게 논의되었다. SIGPIPE가 발생하는 다른 원인은 당신이 연결되지 않은 소켓에 출력을 시도했을 때 발생한다. 11. 8. 5. 1절 [Sending Data] 참조.

매크로 : int SIGUSR1

매크로 : int SIGUSR22

SIGUSR1 과 SIGUSR2 시그널들은 당신이 원하는 어떤 방법을 사용하지 못하도록 한다. 그들은 프로세스간 통신을 위해서 유용하다. 그들 시그널을 보통 심각하기 때문에 당신은 그 시그널을 받은 프로그램에서 그들은 위한 시그널 처리를 해야할 것이다. SIGUSR1 과 SIGUSR2에 대한 사용예는 21. 6. 2절 [Signaling Another Process] 참조.

 

21. 2. 7 비표준 시그널

특정한 운영체제는 위에 설명되지 않은 부가적인 시그널들을 지원한다. ANSI C 표준은 시그널들의 명칭을 `SIG'로 시작하는 대문자로 예약하였다. 당신은 당신의 특정한 운영체제를 위한 헤더파일이나 그 운영체제가 지원하고 있는 시그널을 발견하는 프로세서의 타입 등에 대한 것에 조언을 구할 수 있다. 예를 들어, 어떤 시스템은 하드웨어 트랩에 해당하는 여분의 시그널들을 제공한다. 보통 지원되는 어떤 다른 종류의 시그널들은 CPU 시간과 파일 시스템 사용에 대한 제한을 가하거나, 터미널 구성을 비동기적으로 변경하는 것과 같은 것을 위해 사용되어진다. 시스템들은 또한 표준 시그널 이름의 별칭(aliases)이 되는 시그널 이름들을 정의하고 있다.

당신은 당신이 이해하고 있는 정의된 시그널들을 위한 디폴트 동작을 (또는 쉘에 의해 작동하는 동작) 가정할 수 있고, 당신은 그들에 대해서는 걱정하지 않는다. 당신이 그 시그널의 의미를 알지 못하는 것에 대해서 핸들러를 만들려 시도하거나 알지 못하는 시그널을 무시하거나 블록하는 것은 좋지 못한 생각이다. 여기에 일반적으로 운영체제에서 사용되고 있는 약간의 다른 시그널에 대한 것이 있다.

SIGCLD

SIGCHLD의 오래된 명칭.

SIGTRAP

기계상의 중단점 명령에 의해 발생 된다. 디버거에 의해 사용된다. 디폴트 동작은 코어를 덤프하는 것이다.

SIGIOT

PDP-II "iot" 명령에 의해 발생 된다; SIGABRT와 동등하다. 디폴트 동작은 코어 덤프하는 것이다.

SIGEMT

에뮬레이터를 트랩한다; 어떤 충족되지 못한 명령으로부터의 결과이다. 그것은 프로그램 에러 시그널이다.

SIGSYS

좋지 못한 시스템 호출; 실행된 운영체제를 트랩하기 위한 명령이지만, 그것을 수행하도록 하는 시스템 호출을 위한 코드 번호가 유용하지 않다. 이것은 프로그램 에러이다.

SIGPOLL

이것은 SIGIO와 많이 또는 덜 유사한, 시스템 V 시그널 명칭이다.

SIGXCPU

CPU 시간 제한이 초과되었다. 이것은 배치 프로세싱을 위해서 사용되어진다. 디폴트 동작은 프로그램 종료이다.

SIGXFSZ

파일 크기 제한이 초과되었다. 이것은 배치 프로세싱을 위해서 사용되어진다. 디폴트 동작은 프로그램 종료이다.

SIGWINCH

윈도우 크기를 변경한다. 이것은 스크린에서 현재 윈도우의 크기가 변경되었을 때 어떤 시스템에서 발생되어진다. 디폴트 동작은 그것을 무시하는 것이다.

 

21. 2. 8 시그널 메시지

우리는 자식 프로세스를 종료한 시그널을 설명하는 메시지를 쉘이 프린트하는 것에 대해서는 위에서 잠깐 언급했다. 시그널을 설명하는 메시지를 프린트하는 깨끗한 방법은 strsignal 과 psignal 함수들을 사용하는 것이다. 그들 함수들은 설명하려는 시그널의 종류를 지정하기 위해서 시그널 번호를 사용한다. 시그널번호는 자식 프로세스의 종료상황으로부터(23. 6절 [Process Compltion] 참조)오거나 또는 같은 프로세스 안에 있는 시그널 핸들러로부터 올 것이다.

함수 : char * strsignal (int signum)

이 함수는 시그널 signum을 설명하고 있는 메시지를 포함하고 있는 정적으로 할당된 문자열에 대한 포인터를 반환한다. 당신은 이 문자열의 내용을 갱신할 수 없다. 그리고 그것은 연속된 호출에 의해서 덧씌워질 수 있으므로, 당신은 만일 그것을 나중에 참조 할 필요가 있다면 따로 저장해야할 것이다. 이 함수는 GNU확장으로 헤더파일`string. h'에 선언되어 있다.

함수 : void psignal (int signum, const char *message)

이 함수는 표준 에러 출력 스트림 stderr에 시그널 signum을 설명하는 메시지를 프린트한다. 7. 2절 [Standard Streams] 참조. 만일 당신이 널 포인터이거나 또는 빈 문자열인 message를 가지고 psignal을 호출하면, psignal은 단지 새줄을 하나 덧붙여서 signum에 해당하는 메시지를 프린트한다.
 
만일 당신이 널이 아닌 message 인수를 공급하면, psignal은 이 문자열로 그 출력의 앞에 놓는다. 그것은 signum에 해당하는 문자열로부터 메시지를 분리하기 위한 공백이나 콜론을 더한다. 이 함수는 BSD를 위한 것이고, 헤더파일 `stdio. h'에 선언되어 있다. 또한 다양한 시그널 코드들을 위한 메시지를 포함하고 있는 배열 sys_siglist가 있다. 이 배열은 strsiganl과 달리 BSD 시스템 상에 존재한다


21. 3 시그널 동작 정하기

시그널을 위한 동작을 변경하기 위한 가장 간단한 방법은 signal함수를 사용하는 것이다. 당신은 내장된(built-in) 동작을 지정하거나, 핸들러를 만들 수 있다. GNU 라이브러리는 또한 좀더 다양한 기능을 가진 sigaction 도구를 사용한다. 이 절은 두 개의 도구들에 대한 설명과 언제 이것을 사용할 지에 대한 제안을 한다.

 

21. 3. 1 기본 시그널 처리

signal 함수는 특정한 시그널을 위한 동작을 만들기 위한 간단한 인터페이스를 제공한다. 그 함수와 연관된 매크로들은 헤더파일 `signal. h'에 선언되어 있다.

데이터 타입 : sighandler__t

이것은 시그널 핸들러 함수들의 타입이다. 시그널 핸들러들은 시그널 번호를 지정하기 위해서 정수인수를 하나 취하고, 반환 타입으로는 void 형을 가진다. 그래서 당신은 다음과 같이 핸들러 함수를 정의할 수 있다.

void handler (int signum) { . . . }

이 데이터 타입을 위한 sinhandler_t는 GNU 확장이다.

함수 : sighandler_t signal (int signum, sighandler_t action)

signal 함수는 시그널 signam을 위한 동작을 action으로 만든다. 첫 번째 인수, signum은 당신이 제어하기 원하는 행동을 가진 시그널을 시그널 번호로써 지정한다. 시그널 번호를 지정하기 위한 적당한 방법은 21. 2절 [Standard Signals] 에서 설명된 심볼릭 시그널 이름들 중 하나를 사용하는 것이다_주어진 시그널의 종류를 위한 숫자 코드들은 서로 다른 운영체제에서는 변화할 수 있기 때문에 명백한 숫자를 사용하는 것을 삼가라.
 
두 번째 인수, action은 시그널 signum을 위해 사용하는 동작을 지정한다. 다음중의 하나가 사용될 수 있다.

SIG_DFL

SIG_DFL 은 특정한 시그널을 위한 디폴트 동작을 지정한다. 다양한 종류의 시그널들을 위한 디폴트 동작들은 21. 2절 [Standard Signals] 에 나와있다.

SIG_IGN

SIG_IGN은 시그널이 무시되도록 정한다. 당신의 프로그램은 심각한 사건들을 표현하거나, 또는 종료를 요청하는데 사용되는 시그널들은 보통무시하지 않을 것이다. 당신은 전혀 SIGKILL 또는 SIGSTOP신호를 무시할 수 없다. 당신은 SIGSEGV와 같은 프로그램 에러 시그널들을 무시할 수 있지만, 에러를 무시하는 것이 실행을 계속하도록 프로그램을 가능하게 만드는 것은 아니다. SIGINT, SIGQUIT 그리고 SIGTSTP와 같은 사용자의 요청을 무시하는 것은 사용자를 불쾌하게 만든다. 당신이 프로그램의 어떤 부분이 실행되는 동안에 시그널이 배달되는 것을 원하지 않을 때, 그들을 블록하기 위해서 사용하는 것이지 그들을 무시하는 것이 아니다. 21. 7절 [Blocking Signals] 참조.

handler

어떤 시그널이 배달되었을 때 이 핸들러가 작동하도록 하기 위해서 당신의 프로그램에 핸들러 함수의 주소를 공급한다. 시그널 핸들러 함수를 정의하기에 대한 상세한 정보는 21. 4절 [Defining Handlers] 참조. 만일 당신이 SIG_IGN으로 시그널을 위한 동작을 설정했거나, 또는 SIG_DFL로 설정하고 디폴트 동작이 시그널을 무시하는 것이라면 어느 미해결된 시그널들은 버려진다( 심지어 그들이 블록되었을 지라도 ). 미해결인 시그널들을 버리는 것은, 심지어 당신이 그 시그널을 위한 다른 동작을 지정하고 블록을 해제하도록 연속적으로 정할지라도 그들이 결코 배달되어지지 않을 것임을 의미한다.
 
signal 함수는 정해진 signum 시그널을 위해서 효력이 있었던 동작을 반환한다. 당신은 이 값을 저장할 수 있고 나중에 다시 signal을 호출함으로써 그것을 다시 반환한다. 만일 signal이 요청을 받아들일 수 없다면, 그것은 대신에 SIG_ERR을 반환한다.
 
다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

EINVAL

당신은 유용하지 않은 signum을 지정하였거나; 또는 SIGKILL 이나 SIGSTOP를 위한 핸들러를 제공하거나 무시하려 시도했다.
 
어떤 심각한 시그널이 발생했을 때 임시파일들을 지우기 위한 핸들러를 설정하는 간단한 예제이다.
#include <signal. h>
 
void
termination_handler (int signum)
{
struct temp_file *p;
 
for (p = temp_file_list; p; p = p->next)
unlink (p->name);
}
 
int
main (void)
{
. . .
if (signal (SIGINT, termination_handler) == SIG_IGN)
signal (SIGINT, SIG_IGN);
if (signal (SIGHUP, termination_handler) == SIG_IGN)
signal (SIGHUP, SIG_IGN);
if (signal (SIGTERM, termination_handler) == SIG_IGN)
signal (SIGTERM, SIG_IGN);
. . .
}

주어진 시그널이 무시되도록 미리 설정되어졌다면, 이 코드는 그 설정을 바꾸는 것을 피함을 기억하라. 이것은 비-작업-제어 쉘들이 자식 프로세스가 시작될 때 어떤 시그널들을 종종 무시하기 때문이고, 그리고 이것을 고려하는 것은 자식 프로세스에게는 중요하다. 우리는 이 예제 프로그램이 디버깅을 위해서(코어 덤프 파일) 정보를 제공하도록 만들어졌기 때문에 프로그램 에러 시그널이나 SIGQUIT를 처리하지 않고, 임시 파일들은 유용한 정보를 가질 것이다.

함수 : sighandler_t ssignal (int signum, sighandler_t action)

ssignal 함수는 signal과 같은 일을 한다; 이것은 오직 SVID와의 호환성을 위해서 제공되어졌다.

매크로: sighandler_t SIG__ERR

이 매크로의 값은 에러를 지적하는 signal함수로부터의 반환값으로써 사용되어진다.

 

21. 3. 2 진보된 시그널 처리

sigaction 함수는 signal과 같은 기본 효과를 갖는다: 한 시그널이 어떻게 프로세스에 의해 처리될 것인지를 정하는. sigaction은 조금은 복잡하지만, 더 많은 제어를 제공한다. 특별하게, sigacion은 시그널이 언제 발생되고 어떻게 그 핸들러가 호출될 것인지에 대해 제어를 할 수 있는 부가적인 플래그를 지정하도록 허용한다. sigaction 함수는 `signal. h'에 선언되어 있다.

데이터 타입 : struct sigaction

struct sigacion 타입의 구조체들은 어떻게 특정한 시그널을 처리 할 것인지에 대한 모든 정보를 지정하기 위해서 sigaction 함수에서 사용된다. 이 구조체는 적어도 다음의 멤버들을 갖고 있다.

sighandler_t sa_handler

이것은 signal 함수의 action인수와 같은 방법으로 사용되어진다. 그 값은 SIG_DFL, SIG_IGN 또는 함수 포인터가 될 수 있다. 21. 3. 1절 [Basic Signal Handling] 참조.

sigset_t sa_mask

이것은 핸들러가 작동되고 있는 동안 블록될 시그널의 집합을 설정한다. 블록킹에 대한 것은 21. 7. 5절 [Blocking for Handler] 에 설명되어 있다. 시그널이 배달되었을 때 핸들러가 작동되기 전에 디폴트로써 자동적으로 블록됨을 알아둬라; 이것은 sa_mask에 있는 값에 상관하지 않는다. 만일 당신이 어떤 시그널이 핸들러 안에서 블록되어지지 않기를 원한다면, 당신은 핸들러 안에 있는 코드에 그것을 블록하지 않을 것임을 적어야 한다.

int sa_flags

이것은 시그널의 동작에 영향을 미칠 수 있는 다양한 플래그들을 지정한다. 이들에 대한 것은 21. 3. 5절 [Flags for Sigaction] 에 좀더 자세하게 설명되어 있다.

함수 : int sigaction(int signum, const struct sigaction *action, struct sigaction *old_action)

action 인수는 시그널 signum을 위한 새로운 동작을 준비하기 위해서 사용되고, old_action 인수는 이 심볼과 연관된 이전의 동작에 대한 정보를 반환하기 위해 사용된다. (즉, old_action은 signal 함수의 반환값과 같은 목적을 갖는다.
 
당신은 그 시그널에 영향을 미쳤던 이전의 동작이 무엇이었는지를 알 수 있을 뿐만 아니라 나중에 만일 당신이 원한다면 그 동작을 다시 반환할 수도 있다. ) action 또는 old_action 중의 하나는 널 포인터가 될 수 있다. 만일 old_action 이 널 포인터라면, 이전동작(old action)에 대한 정보를 반환하는 것이 생략된다. 만일 action이 널 포인터라면, 시그널 signum과 연관된 동작이 변경되지 않는다; 이것은 원래의 시그널이 가진 동작은 변경함이 없이 시그널을 처리할 수 있음을 허용한다.
 
sigaction으로부터의 반환값은 만일 성공하면 0이고, 실패하면 -1을 반환한다.
다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EINVAL

signum인수가 유용하지 않거나, 당신이 SIGKILL 또는 SIGSTOP 시그널을 무시하거나 트랩 하려고 시도하였다.

 

21. 3. 3 signal 과 sigaction 의 상호작용

한 단일한 프로그램 안에서 signal 과 sigaction 함수들을 모두 사용하는 것이 가능하지만, 그들은 완전히 다른 방법들로 서로 영향을 미칠 수 있기 때문에 주의해야만 한다.

sigaction 함수는 signal 함수보다는 좀더 많은 정보를 지정하기 때문에, signal로부터의 반환값은 sigaction이 표현할 수 있는 범위를 표현할 수 없다. 그렇지만, 만일 당신이 어느 동작을 저장하고 나중에 다시 그 동작을 재건하기 위해서 signal을 사용한다면, sigaction을 통해서 만들어졌던 핸들러는 적당하게 재건되어질 수 없을 것이다.

이러한 문제를 피하기 위해서, 핸들러가 전혀 sigaction을 사용하지 않은 프로그램이라고 할 지하도 핸들러는 저장하고 반환하기 위해서는 항상 sigaction을 사용하라. sigaction이 좀더 일반적이기 때문에, 원래 siganl 또는 sigaction을 가지고 만들었는지에 상관없이, 어느 동작을 저장하고 재건하기 위해서 sigaction을 사용하는 것이 더 적당할 수 있다.

만일 signal을 사용해서 어떤 동작을 만들었고 그 다음 sigaction을 사용해서 그것을 시험한다면, 핸들러 주소는 당신이 signal에서 지정했던 것과 같지 않은 것을 얻게 될 것이다. 그것은 심지어 signal에 action인수로써 사용하기에도 적당하지 않게 될 것이다. 그러나 당신은 sigaction의 인수로써 그것을 사용하고 적용할 수 있다. 그래서, 단일한 프로그램 안에서는 시종일관 한가지 또는 다른 한가지의 메커니즘을 고수하는 것이 한결 더 낫다.

 
이식성 노트 : 기본 signal 함수는 ANSI C에서 지원되고, sigaction함수는 POSIX. 1 표준에서 지원하고 있다. 만일 당신이 비-POSIX 계열의 시스템과의 호환성에 관심이 있다면, 당신은 대신에 signal을 사용하라.

 

21. 3. 4 sigaction 함수 예제

21. 3. 1절 [Basic Signal Handling] 에서, signal을 사용해서 종료 시그널들을 위한 간단한 핸들러를 만드는 예제를 보여주었다. 다음은 sigaction을 사용하는 같은 예제이다.

#include <signal. h>
 
void
termination_handler (int signum)
{
struct temp_file *p;
 
for (p = temp_file_list; p; p = p->next)
unlink (p->name);
}
 
int
main (void)
{
. . .
struct sigaction new_action, old_action;
 
/* 새로운 동작을 지정할 구조체를 준비하라. */
new_action. sa_handler = termination_handler;
sigemptyset (&new_action. sa_mask);
new_action. sa_flags = 0;
 
sigaction (SIGINT, NULL, &old_action);
if (old_action. sa_handler != SIG_IGN)
sigaction (SIGINT, &new_action, NULL);
sigaction (SIGHUP, NULL, &old_action);
if (old_action. sa_handler != SIG_IGN)
sigaction (SIGHUP, &new_action, NULL);
sigaction (SIGTERM, NULL, &old_action);
if (old_action. sa_handler != SIG_IGN)
sigaction (SIGTERM, &new_action, NULL);
. . .
}

이 프로그램은 요구된 파라미터로 단지 new_action을 로드하고 sigaction을 호출할 때 그것을 인수로써 사용한다. sigemptyset의 사용에 대한 것은 나중에 설명되었다. 21. 7절 [Blocking Signals] 참조. signal을 사용하는 예제에서, 미리 무시되도록 설정된 시그널들을 처리하는 것을 피했다. 여기서 새로운 동작을 지정하지 않고 현재의 동작을 시험하는 것을 허용하는 sigaction을 사용해서, 순간적으로 시그널핸들러가 변경되는 것을 피할 수 있다.

다음은 다른 예제이다. 이것은 동작을 변경하지 않고 SIGINT를 위해서 현재의 동작에 대한 정보를 구한다.

struct sigaction query_action;
 
if (sigaction (SIGINT, NULL, &query_action) < 0)
/* 에러가 발생하면 sigaction은 -1을 반환한다. */
else if (query_action. sa_handler == SIG_DFL)
/* SIGINT는 원래 자신이 가지고 있던 디폴트 동작으로 처리되었다. */
else if (query_action. sa_handler == SIG_IGN)
/* SIGINT는 무시되었다. */
else
/* 프로그래머가 만든 핸들러가 효력을 발휘하였다. */

 

21. 3. 5 sigaction을 위한 플래그

sigaction 구조체의 멤버 sa_flags는 특별한 기능을 위한 것이다. 대부분의 경우에, SA_RESTART가 이 필드에서 사용하기에 가장 좋은 값이다. sa_flags의 값은 비트마스크로써 해석되어진다. 그래서, 당신은 당신이 원하는 플래그를 설정하거나 선택할 수 있고, sigaction 구조체의 sa_flags 멤버 안에 그 결과를 저장한다. 각각의 시그널 번호는 자신만의 플래그 설정을 갖는다. sigaction을 호출하는 것은 특정한 시그널 번호의 영향을 받고, 당신이 지정한 플래그들은 오직 특정한 시그널에만 적용된다.

GNU C 라이브러리에서, signal로 핸들러는 만드는 것은 당신이 siginterrupt로 만들었던 설정에 의존한 값을 갖는 SA_RESTART를 제외하고는 모두 0으로 플래그를 설정한다. 21. 5절 [Interrupter Primitives] 를 참조하라. 이들 매크로들은 헤더파일 `signal. h'에 정의되어 있다.

매크로 : int SA__NOCLDSTOP

이 플래그는 SIGCHLD 시그널에서만 유용하다. 그 플래그가 설정되었을 때, 종료된 자식 프로세스를 위한 시그널은 배달하지만, 멈추어있는 자식프로세스를 위한 시그널은 배달하지 않는다. 원래 SIGCHLD를 위한 디폴트 설정은 종료된 자식 프로세스와 멈춘 자식 프로세스 둘다를 위한 시그널을 배달하는 것이다. 이 플래그가 설정되면 SIGCHLD를 제외한 다른 시그널에는 아무런 영향을 주지 않는다.

매크로 : int SA__ONSTACK

만일 어떤 특정한 시그널 번호를 위해서 이 플래그가 설정되면, 시스템은 그런 종류의 시그널이 배달되었을 때 시그널 스택을 사용한다. 21. 9절 [BSD Signal Handling] 참조

매크로 : int SA__RESTART

이 플래그는 어떤 기본동작 (open, read, 또는 write와 같은)이 진행되고있는 동안 배달된 시그널을 어떻게 할 것인지를 제어하고, 시그널 핸들러는 정상적으로 반환한다. 두 개의 선택권을 가진다. 라이브러리 함수가 계속될 수 있거나, 또는 에러코드 EINTR을 사용해서 실패를 반환할 수 있다. 그러한 선택은 배달된 특정한 종류의 시그널에 따라서 SA_RESTART에 의해 제어된다. 만일 그 플래그가 설정되면, 핸들러로부터의 반환은 라이브러리 함수를 다시 계속한다. 만일 그 플래그의 설정이 해제되면, 핸들러로부터의 반환은 그 함수가 실패하도록 만든다. 21. 5절 [Interrupted Primitives] 참조.

 

21. 3. 6 초기 시그널 동작들

새로운 프로세스가 만들어질 때(23. 4절 [Creating a Process] 참조. ), 부모 프로세스로 부터 시그널들의 처리를 상속받는다. 그렇지만, 당신이 exec 함수(23. 5절 [Executing a File] 참조. )를 사용해서 새로운 프로세스 이미지를 로드할 때, 어느 시그널들은 그들의 원래의 동작으로 환원하도록 SIG_DFL을 사용해서 당신 자신의 핸들러를 정의해야만 한다. (만일 당신이 이것에 대해서 조금만 생각한다면, 이것을 이해할 수 있다; 원래의 프로그램으로부터 온 처리 함수들은 그 프로그램을 위한 정의이고, 새로운 프로그램 이미지의 주소 공간에는 심지어 존재하지도 않는다. ) 물론, 새로운 프로그램은 자신의 핸들러를 만들 수 있다.

어떤 프로그램이 쉘에 의해 실행될 때, 쉘은 보통 적당한 SIG_DFL 또는 SIG_IGN을 사용해서 자식 프로세스를 위한 초기동작을 설정한다. 그것은 당신이 새로운 시그널 핸들러를 만들기 전에 쉘이 SIG_IGN으로 초기동작을 준비하지 않은 것이 확실한지 체크하는 것은 좋은 생각이다. 다음은 현재 무시되지 않고 있는 SIGHUP 시그널을 위한 핸들러를 어떻게 만드는지에 대한 예제이다.

. . .
struct sigaction temp;
 
sigaction (SIGHUP, NULL, &temp);
 
if (temp. sa_handler != SIG_IGN)
{
temp. sa_handler = handle_sighup;
sigemptyset (&temp. sa_mask);
sigaction (SIGHUP, &temp, NULL);
}


21. 4 시그널 핸들러 정의하기

이 절은 signal 이나 sigaction 함수들을 사용해서 만들 수 있는 시그널 핸들러 함수를 어떻게 쓸것인지에 대해서 설명하고 있다. 시그널 핸들러는 프로그램의 다른 부분과 함께 컴파일하는 함수일 뿐이다. 직접적으로 그 함수를 불러내는 대신에, 시그널이 도착했을 때 그 핸들러를 호출하도록 운영체제에게 알리는 signal 이나 sigaction 함수를 사용한다. 이것이 핸들러를 만드는 것이다. 21. 3절 [Signal Actions] 참조. 다음은 당신이 시그널 핸들러 함수에서 사용할 수 있는 두 개의 기본 범주들이다.

어떤 전역 데이터 구조체에 의해서 도착된 시그널을 기록해두고 그 다음 정상적인 반환을 하는 핸들러 함수를 가질 수 있다.

프로그램을 종료시키거나 그 시그널의 원인이 된 상황으로부터 회복할 수 있는 지점으로 제어를 옮기는 핸들러 함수를 가질 수 있다.

당신이 핸들러 함수를 작성할 때 핸들러 함수는 비동기적으로 호출되어질 수 있기 때문에 각별한 주의가 필요하다. 즉, 핸들러함수는 예측할 수 없이, 프로그램의 어느 지점에서든지 호출되어질 것이다. 만일 매우 짧은 간격을 두고 두 개의 시그널이 도착한다면, 한 개의 핸들러는 다른 핸들러 안에서 실행할 수 있다. 이 절은 당신의 핸들러는 무엇을 하고, 무엇을 피해야 하는지를 설명한다.

 

21. 4. 1 반환하는 시그널 핸들러

정상적인 반환을 하는 핸들러는 SIGALRM 과 입/출력과 시그널을 사용한 프로세스간 통신과 같은 시그널에 보통 사용되어진다. 그러나 SIGINT를 위한 핸들러는 가능한 시간에 프로그램이 분기하도록 플래그를 설정한 후에 정상적으로 반환한다.

프로그램 에러 시그널을 위한 핸들러로부터 정상적인 반환을 하도록 하는 것은 안전하지 않다, 왜냐하면 핸들러 함수가 반환할 때 프로그램의 동작은 프로그램 에러 후에 무엇을 할 것인지 정의되지 않았기 때문이다. 21. 2. 1절 [Program Erroe Signals] 참조.

정상적으로 반환하는 핸들러들은 어떤 전역 변수를 갱신해야만 한다. 전형적으로, 그 변수는 정상적으로 작동하는 동안에 프로그램에 의해 주기적으로 시험되는 것이다. 데이터타입은 21. 4. 7절 [Atomic Data Access] 에서 설명된 sig_atomic_t가 된다.

다음은 한 개의 프로그램과 같은 간단한 예제이다. 이 프로그램은 SIGALRM 시그널이 도착한 것을 발견할 때까지 루프의 몸체를 실행한다. 이 기술은 시그널이 도착하면 그 루프를 분기하기 전에 어떤 동작을 완성하도록 진행과정에서 상호작용을 허용하기 때문에 유용하다.

#include <signal. h>
#include <stdio. h>
#include <stdlib. h>
 
/* 이 플래그는 메인 루프의 종료를 제어한다. */
volatile sig_atomic_t keep_going = 1;
 
/* 시그널 핸들러는 단지 그 플래그를 소거하고 그 자체를 다시 가능하게 한다. */
void
catch_alarm (int sig)
{
keep_going = 0;
signal (sig, catch_alarm);
}
 
void
do_stuff (void)
{
puts ("Doing stuff while waiting for alarm. . . . ");
}
 
int
main (void)
{
/* SIGALRM 시그널을 위한 핸들러를 만든다. */
signal (SIGALRM, catch_alarm);
 
/* 잠시동안 알람이 멈추도록 설정한다. */
alarm (2);
 
/* 종료할 때를 알기 위해서 while에서 플래그를 체크한다. */
while (keep_going)
do_stuff ();
return EXIT_SUCCESS;
}

 

21. 4. 2 프로세스를 종료시키는 핸들러

프로세스를 종료시키는 핸들러 함수들은 전형적으로 소거 명령이나 프로그램 에러 시그널로부터의 복구와 인터럽트가 원인이 된 곳에 사용된다. 프로세스를 종료하도록 하는 핸들러를 만드는 가장 깨끗한 방법은 핸들러 실행의 첫 번째에서 같은 시그널이 발생되도록 하는 것이다. 다음과 같이.

volatile sig_atomic_t fatal_error_in_progress = 0;
 
void
fatal_error_signal (int sig)
{
 
/* 이 핸들러는 여러 개의 시그널에 대하여 처리하는 것이므로, 어떤 종류의 시그널이 배달될 때마다 반복적으로 핸들러가 불려질 것이다. 그것을 기억하도록 정적변수를 사용하라. */
if (fatal_error_in_progress)
raise (sig);
fatal_error_in_progress = 1;
 
/* 이제 동작들을 정리하자 :
- 터미널 모드를 재설정한다.
- 자식 프로세스를 kill한다. (어~~ 쌀벌해. . )
- 록 파일들을 제거하자. */
. . .
 
/* 이제 시그널을 다시 일으키자. 그 동안 시그널은 블록되어져 있었고, 이제 디폴트 처리로써, 프로세스를 종료하도록 하는 그 시그널을 받을 것이다. 우리는 단지 exit 나 abort만을 호출할 수도 있지만, 시그널을 재 발생시키면 프로세스의 정확한 상황으로 반환을 설정한다. */
raise (sig);
}  

21. 4. 3 핸들러 안에서 비지역 제어 이동

당신은 setjmp 와 longjmp 기능을 사용해서 시그널 핸들러의 외부로 제어의 비지역 이동을 할 수 있다( 20장 [Non-Local Exits] 참조. ) 핸들러가 비지역 제어 이동을 할 때, 실행 중에 있던 프로그램의 그 부분은 계속되지 않을 것이다. 만일 프로그램의 그 부분이 중요한 데이터 구조체를 갱신 중에 있었다면, 그 데이터 구조체는 여전히 완벽하게 처리되지 못한 상태로 남게될 것이다. 프로그램이 종료되지 않는다면, 위와 같은 문제는 나중에 발견될지도 모른다.

이러한 문제를 피하기 위한 두 개의 방법이 존재한다. 한가지는 중요한 데이터를 갱신하는 프로그램 부분을 위해서는 시그널을 블록하는 것이다. 블록된 시그널은 그 블록이 해제된 후에 배달되어지고, 그때는 이미 중요한 데이터 갱신은 끝난 상태가 된다. 21. 7절 [Blocking Signals] 참조. 다른 방법은 시그널 핸들러 안에서 중요한 데이터의 구조체들을 재-초기화하거나, 그들의 값을 모순이 없도록 만드는 것이다. 다음은 한 개의 전역변수의 재초기화를 보여주는 개략적인 예제이다.

#include <signal. h>
#include <setjmp. h>
 
jmp_buf return_to_top_level;
 
volatile sig_atomic_t waiting_for_input;
 
void
handle_sigint (int signum)
{
/* 우리는 시그널이 도착했을 때는 입력을 받기 위해서 기다리겠지만, 제어를 옮길 때는 더 이상 기다리지 않는다. */
waiting_for_input = 0;
longjmp (return_to_top_level, 1);
}
 
int
main (void)
{
. . .
signal (SIGINT, sigint_handler);
. . .
while (1) {
prepare_for_command ();
if (setjmp (return_to_top_level) == 0)
read_and_execute_command();
}
}
 
/* 이것이 여러 명령문에서 사용되는 서브루틴이라고 생각하자. */
char *
read_data ()
{
if (input_from_terminal) {
waiting_for_input = 1;
. . .
waiting_for_input = 0;
} else {
. . .
}
}

 

21. 4. 4 핸들러가 실행되고 있는 동안 도착한 시그널들

시그널 핸들러 함수가 실행되고 있을 때 도착한 다른 시그널이 있다면 무슨 일이 발생할까? 한 특정한 시그널을 위한 핸들러가 호출되었을 때, 핸들러가 반환할 때까지 그 시그널은 보통 블록된다. 만일 같은 종류의 두 개의 시그널이 서로 가까운 시간에 도착한다면, 두 번째 것은 첫 번째 것이 처리될 때까지 그냥 보유하고 있을 것이다. ( 만일 당신이 이러한 형태의 더 많은 시그널이 도착하도록 허용하기를 원한다면, 핸들러는 sigprocmask를 사용해서 시그널을 명백하게 블록을 해제할 수 있다; 21. 7. 3절 [Process Signal Mask] 참조. )

그렇지만, 당신의 핸들러는 다른 종류의 시그널의 배달에 의해서는 여전히 인터럽트 되어질 수 있다. 이것을 피하기 위해서, 당신은 sigaction에 인수로써 사용하는 action 구조체의 sa_mask 멤버를 사용해서 핸들러가 실행되는 동안 블록되어질 시그널을 명백하게 지정할 수 있다. 그들 시그널은 호출 된 핸들러를 위한 시그널에 더해져 있고, 다른 시그널들은 보통 프로세스에 의해서 블록되어진다. 21. 7. 5절 [Blocking for Handler] 참조.

 
이식성 노트 : 만일 당신의 프로그램이 완전히 System V Unix상에서 작업하기를 원할 때, 비동기적인 발생이 예상되는 시그널을 위한 핸들러를 만들려면 항상 sigaction을 사용하라. 다른 시스템에서는, 핸들러에서 하는 시그널의 처리는 SIG_DFL로 시그널이 가진 원래의 동작으로 되돌려지도록 만들어져있고, 핸들러는 실행될 때마다 그 자체를 다시 만들어야만 한다. 이 것은 실제로 시그널이 연속적으로 도착할 수 없을 때 작업하기는 불편하다. 하지만 다른 시그널이 즉시 도착할 수 있다면, 그것은 다시 핸들러를 다시 정하지 않아도 된다. 그러면 두 번째 시그널은 프로세스를 종료시키는, 디폴트 처리로 받게될 것이다.

 

21. 4. 5 한가지로 합병한 서로 밀접한 시그널들

만일 당신의 시그널 핸들러가 전혀 호출될만한 기회도 갖기 전에, 당신의 프로세스에 같은 종류의 시그널이 여러 개 배달되었다면, 그 핸들러는 오직 한 개의 시그널이 도착한 것처럼 호출되어질 것이다. 실제로, 그 시그널들은 한 개로 합병한다. 이 상황은 시그널이 블록되었을 때나, 또는 멀티프로세싱 환경에서 시그널이 도착했는데 시스템이 다른 프로세스의 실행 때문에 바쁠 때 발생할 수 있다. 이것이 의미하는 것은, 예를 들어, 당신은 발생한 시그널의 개수를 세는 시그널 핸들러의 사용을 신뢰할 수 없다. 오로지 당신이 구분할 수 있는 것은 과거의 주어진 시간동안에 적어도 한 개의 시그널이 도착했는지, 또는 도착하지 않았는지를 구분하는 것만을 신뢰할 수 있다. 다음은 자식 프로세스가 발생시킨 SIGCHLD의 개수와는 같지 않을지도 모르는 SIGCHLD 시그널의 개수를 실제처럼 대치하는 핸들러의 예제이다. 그것은 프로그램이 다음처럼 구조체를 연결하여 자식 프로세스의 모두를 추적하고 있다고 가정한다.

structprocess
{
struct process *next;
/* 자식 프로세스의 프로세스 ID */
int pid;
/* 자식 프로세스가 출력을 하는 파이프나 가상 터미널의 기술자 */
int input_descriptor;
/* 만일 이 프로세스가 멈추거나 종료된다면 0이 아니다. */
sig_atomic_t have_status;
/* 자식 프로세스의 상황; 실행중이면 0, 그렇지 않으면 그 status는 waitpid로부터의 값이다. */
int status;
};
struct process *process_list;
 
다음 예제는 과거의 어떤 시간동안에 시그널이 도착했는지를 지적하는 플래그를 사용한다_그때마다 프로그램은 마지막에 그 플래그를 0으로 소거한다.
/* 0이 아닌 값은 process_list의 항목에서 상황이 변환된 자식 프로세스를 발견했음을 의미한다. */
int process_status_change;
다음은 핸들러 자체이다.
 
void sigchld_handler (int signo)
{
int old_errno = errno;
 
while (1) {
register int pid;
int w;
struct process *p;
 
/* 정해진 결과를 얻을 때까지 상황을 물어보아라 */
do{
errno = 0;
pid = waitpid (WAIT_ANY, &w, WNOHANG |
WUNTRACED);
}while (pid <= 0 && errno == EINTR);
 
if (pid <= 0 ) {
/* 실패의 실제 의미는 더 이상 멈추거나 종료될 자식프로세스가 없음을 의미하고, 그래서 반환한다. */
errno = old_errno;
return;
}
 
/* 우리에게 신호를 보냈던 프로세스를 찾아서, 그 상황을 기록하라 */
 
for (p = process_list; p; p = p->next)
if (p->pid == pid) {
p->status = w;
/* 보려하는 데이터를 가진 상황 필드를 지적하라. 우리는 그것이 저장된 후에 이것을 한다. */
p->have_status = 1;
 
    /* 만일 프로세스가 종료되었다면, 출력을 위한 기다림을 멈추어라 */
    if (WIFSIGNALED (w) || WIFEXITED (w)) {
    if (p->input_descriptor)
    FD_CLR (p->input_descriptor, &input_wait_mask);
     
    /* 프로그램은 process_list 안에 어떤 새로운 것이 있는지 알아보기 위해서 주어진 시간동안에 이 플래그를 체크할 것이다. */
    ++process_status_change;
    }
}
 
/* 우리에게 말할 무언가를 가진 모든 프로세스들을 처리하도록 루프를 돌려라 */
}
}
 
다음은 process_status_change 플래그를 체크하기 위한 적당한 방법이다.
 
if (process_status_change) {
struct process *p;
process_status_change = 0;
for (p = process_list; p; p = p->next)
if (p->have_status) {
. . . Examine p->status . . .
}
}

리스트를 시험하기 전에 플래그를 소거하는 것은 치명적이다; 그렇지 않고, 만일 플래그가 소거되기 전과, 프로세스 리스트의 적당한 요소가 체크된 후에 시그널이 배달된다면, 다음 도착한 시그널이 다시 그 플래그를 설정하기 전까지는 그 상황변화를 알아차릴 수 없다. 당신은 물론 그 리스트를 조사하는 동안 시그널을 블록함으로써 이러한 문제를 피할 수는 있지만, 올바른 순서로 일들을 처리하는 것이 정확함을 보증하기에는 좀더 좋은 방법이다.

프로세스 상황을 체크하는 루프가 그 상황이 유용하게 저장되어졌음이 확인될 때까지 p->status를 조사하는 것을 피한다. 이것은 status가 억세스 되고 있는 도중에 변화될 수 없음을 확실하게 한다. 일단 p->have_status가 설정되면, 그것은 자식 프로세스가 멈추거나 종료했음을 의미하고, 그 어느 경우에도, 프로그램이 주목하고 있는 동안에 다시 멈추거나 종료할 수 없다. 변수를 억세스하고 있는 동안에 인터럽션(interruptions)을 모방하기에 대한 상세한 정보는 21. 4. 7. 3절 [Atomic Usage] 를 참조하라.

다음은 당신이 체크했던 마지막 시간이후 핸들러가 실행되었는지를 시험 할 수 있는 다른 방법이다. 이 기술은 핸들러의 외부에서 결코 변화되지 않을 카운터로 사용한다. 빈도수(count)를 소거하는 대신에, 프로그램은 전의 값을 기억하고 있다가 그 이후에 그 값이 변화되었는지를 보여준다. 이 방법이 유리한 점은 프로그램의 다른 부분들을 독립적으로 체크할 수 있다는 것으로, 각각의 부분은 그 부분을 마지막으로 체크한 이후에 시그널이 있었는지를 체크한다.

sig_atomic_t process_status_change;
 
sig_atomic_t last_process_status_change;
. . .
{
sig_atomic_t prev = last_process_status_change;
last_process_status_change = process_status_change;
if (last_process_status_change != prev) {
struct process *p;
for (p = process_list; p; p = p->next)
if (p->have_status) {
. . . Examine p->status . . .
}
}
}

 

21. 4. 6 시그널 핸들링 과 재진입 불가 함수들

** 역자주 : 재진입(reentrant) : 어떤 모듈을 재진입이 가능하다라고 할 때, 그것은 동시에 두 개 이상의 프로그램에 의하여 공유될 수 있는 모듈을 말한다. 이러한 모듈은 실행 중에 자신의 코드 또는 데이터 영역을 변경시키지 않아야 합니다.

핸들러 함수들은 보통 많은 일을 하지는 않는다. 핸들러 함수에게는 프로그램이 정기적으로 체크하는 외부변수를 설정하는 일 이외에는 아무 것도 하지 않게 하고, 다른 중요한 일들은 프로그램에게 맡기는 것이 좋다. 이것은 핸들러가 예측할 수 없는 시간에_시스템 호출도중, 또는 다중 명령을 요구하는 C연산자의 시작과 끝 사이에_비동기적으로 호출될 수 있기 때문이다.

데이터 구조체가 처리되고 있는 동안 핸들러 함수가 호출되면 데이터 구조체의 상황은 불일치하게 될 것이다. 심지어 한 개의 int 형 변수에서 다른 변수로 값을 복사하는 것조차도 대부분의 기계에서 두 개의 명령어를 취할 수도 있다. 이것은 당신이 시그널 핸들러에서 무언가를 할 때 많은 주의를 해야만 한다는 것을 의미한다.

만일 당신의 핸들러가 어느 전역변수를 억세스할 필요가 있다면, 그 변수들을 휘발성으로 선언하라. 이것은 변수들의 값이 비동기적으로 변화할 것이라고 컴파일러에게 알리고, 그와같은 갱신에 의해 무효로 만들게 될 어떤 최적화를 금한다.

만일 핸들러 안의 어떤 함수를 호출한다면, 그것이 시그널들에 대해서는 재진입성이 있음을 확실히 하거나, 시그널이 함수와 연관된 호출에는 인터럽트 할 수 없음을 확실히 하라.

스택 (stack)상에 존재하지않는 메모리를 사용하는 함수는 비-재진입성이 될 수 없다.

만일 어떤 함수가 정적 변수나 전역변수, 또는 동적으로 할당된 오브젝트를 사용한다면, 그것은 비-재진입성이고, 그 함수를 두 번 호출하면 서로 충돌하게 될 수 있다.

예를 들어, 시그널 핸들러가 gethostbyname을 사용한다고 가정하자. 이 함수는 정적 오브젝트에 그 값을 반환하고, 매번 같은 오브젝트를 다시 사용한다. 만일 gethostbyname이 호출된 동안, 또는 심지어 그 후(하지만 여전히 그 값은 프로그램에서 사용하고 있는 중이다. )라도 그 시그널이 도착하는 일이 발생한다면, 그것은 프로그램이 요청한 그 값을 지워버릴 것이다. 그렇지만, 만일 그 프로그램이 gethostbyname이나 같은 오브젝트에 정보를 반환하는 함수를 사용하지 않거나, 또는 만일 그와같은 것을 사용한다고 해도 그것을 사용할 때 시그널들을 블록한다면, 당신은 안전하다. 라이브러리 함수들의 대부분은 한 고정 오브젝트에 값들을 반환하고 항상 같은 오브젝트를 재 사용하기 때문에 그들은 같은 문제를 발생시킬 가능성이 있다. 이 매뉴얼 안에 있는 함수들에 대한 설명에는 이러한 것들을 항상 언급할 것이다.

만일 어떤 함수가 당신이 공급한 오브젝트를 사용하고 갱신한다면, 그것은 잠재적으로 비-진입성이다. 같은 오브젝트를 사용하고 있는 두 개의 호출은 충돌할 수 있다.

이와 같은 경우는 당신이 스트림을 사용해서 입/출력을 할 때 발생한다. 시그널 핸들러가 fprintf를 사용해서 메시지를 출력한다고 가정하자. 그리고 그 프로그램이 fpintf를 처리하고 있는 도중에 같은 오브젝트를 사용하는 시그널이 배달되었다고 가정하자. 이때 두 개의 호출은 같은 데이터 구조체_스트림 자체_에서 동작하기 때문에 핸들러의 메시지와 프로그램의 데이터는 모두 변조될 것이다. 그렇지만, 만일 핸들러에서 사용하는 스트림이 시그널이 도착하여 동시에 그 스트림이 프로그램에 의해 사용되어질 가능성이 없다는 것을 당신이 알고 있다면, 아무런 문제가 없다. 그리고 만일 프로그램이 다른 스트림을 사용한다면 아무런 문제가 없다.

대부분의 시스템에서, malloc 과 free는 무슨 메모리 블록들이 해제상태에 있는지를 기록하고 있는 정적 데이터 구조체를 사용하기 때문에, 재진입성이 없다. 그렇기 때문에 메모리를 할당하고 해제하는 라이브러리 함수중에 재진입성이 있는 것은 아무 것도 없다. 핸들러에서 메모리를 할당할 필요를 피하기 위한 가장 좋은 방법은 시그널 핸들러에서 사용할 공간을 미리 할당받는 것이다.

핸들러에서 메모리를 해제하는 것을 피하는 가장 좋은 방법은 해제할 오브젝트를 플래그로 표시하거나 기록해두고, 어느 것이 해제되기를 기다리고 있는지를 나중에 프로그램에서 체크하는 것이다. 그러나 이것은 오브젝트들이 개별적으로 존재하는 것이 아니라 서로 연결되어 있고, 같은 일을 하는 다른 시그널 핸들러에 의해서 그것이 인터럽트 되어졌다면, 당신은 오브젝트들 중 하나를 "잃어"버릴 수 있기 때문에 주의를 해야만 한다. GNU 시스템에서, malloc 과 free는 시그널들을 블록하기 때문에 시그널 핸들러에서 사용하는 것은 안전하다. 그렇기 때문에, 시그널 핸들러에서 결과를 위해서 공간을 할당하는 것은 또한 안전하다. obstack 할당 함수들도 당신이 시그널 핸들러의 외부와 내부양쪽에서 같은 obstack을 사용하지 않는다면 안전하다. 재배치(relocating) 할당 함수들( 3. 6절 [Relocating Allocator] 참조. )을 시그널 핸들러 안에서 사용하는 것은 안전하지 않음이 확실하다.

errno를 갱신하는 어떤 함수들은 비-진입성이지만, 당신은 이것을 진입성으로 만들 수 있다: 핸들러에서, errno의 원래 값을 저장하고 정상적으로 반환하기 전에 그것을 반환한다. 이것은 시그널 핸들러 내부에서 발생된 에러들이, 핸들러가 실행되도록 프로그램이 인터럽트 된 순간에 시스템 호출로부터 발생한 에러와 혼동되는 것을 막는다.

이 기술은 일반적으로 응용 가능하다; 만일 당신이 핸들러의 내부에서 메모리의 특정한 오브젝트를 갱신하는 함수를 호출하기 원한다면, 당신은 그 오브젝트를 저장하고 다시 반환함을 통해서 안전하게 구현할 수 있다.

메모리 오브젝트로부터 읽기는 시그널이 배달되어질 때라도 오브젝트에 나타날 수 있는 어떤 값들을 취급할 수 있도록 안전하게 제공되었다. 어떤 데이터 타입에 배정(assignment)할 때, 그 데이터 타입이 원자단위가 아닌 변수에 배정(assignment)하는 "도중에" 핸들러가 실행될 수 있다면 그 배정에는 많은 명령(instruction)이 요구됨을 명심하라.

메모리 오브젝트에 기록하기는 핸들러가 실행되고 있는 순간일지라도 안전하고, 어느 것도 방해되지 않을 것이다.

 

21. 4. 7 원소 데이터 억세스와 시그널 핸들링

당신의 어플리케이션에서 데이터가 원자와 관계가 있던지, 또는 단순한 텍스트이던지, 당신은 원자화가 필요 없는 단일한 데이터를 억세스 하는 요소에 대해서 주의를 해야만 한다. 이것은 단일한 오브젝트를 읽거나 쓰기 위해서는 여러 개의 명령이 필요할 수 있다는 것을 의미한다. 그와같은 경우에, 시그널 핸들러는 오브젝트의 읽기나 쓰기 중간에 실행될 수 있다.

이러한 문제를 커버할 수 있는 세 가지 방법이 있다. 당신은 항상 원자 단위로 억세스되는 데이터 타입을 사용할 수 있다; 억세스를 인터럽트 하여 아무런 부적당한 일이 일어나지 않게 하거나, 또는 인터럽트보다는 좋지는 않지만 억세스동안에 모든 시그널들을 블록하는 등 당신은 주의 깊은 조정을 할 수가 있다.

 

21. 4. 7. 1 비-원자 억세스가 갖는 문제점

다음은 변수를 갱신하는 도중에 시그널 핸들러를 실행하면 무슨 일이 발생하는지를 보여주는 예제이다. (변수 읽기를 인터럽트 하는 것도 역설적인 결과에 이르게 할 수 있지만, 여기서 우리는 쓰기를 보여준다. )

#include <signal. h>
#include <stdio. h>
 
struct two_words { int a, b; } memory;
 
void
handler(int signum)
{
printf ("%d, %d\n", memory. a, memory. b);
alarm (1);
}
 
int
main (void)
{
static struct two_words zeros = { 0, 0 }, ones = { 1, 1 };
signal (SIGALRM, handler);
memory = zeros;
alarm (1);
while (1)
{
memory = zeros;
memory = ones;
}
}

이 프로그램은 계속 번갈아 가면서 0, 1, 0, 1 로 메모리를 채운다; 그 동안, 일초마다, 알람 시그널 핸들러는 현재의 내용을 프린트한다. ( 핸들러 안에서 printf의 호출은 시그널이 발생했을 때 핸들러외부에서 printf가 확실히 호출되어지지 않을 것이므로 이 프로그램은 안전하다. ) 분명히, 이 프로그램은 0 한 쌍과 1 한 쌍을 프린트 할 수 있다. 하지만 그것이 그 프로그램이 할 수 있는 전부가 아니다! 대부분의 기계에서, 메모리에 새로운 값을 저장하기 위해서는 여러 개의 명령을 취하고, 그 값은 동시에 한 워드(word)에 저장된다. 만일 시그널이 그 명령들 사이에 배달된다면, 핸들러는 memory. a는 0이고 memory. b는 1인걸 발견할지 모른다(또는 그의 반대).

한 개의 명령으로 메모리 안에 한 개의 새로운 값을 저장할 수 있는 어떤 기계에서는 인터럽트 될 수 없다. 그 기계들에서, 핸들러는 항상 두 개의 0과 두 개의 1을 프린트 할 것이다.

 

21. 4. 7. 2 원자 형

변수를 억세스할 때 인터럽트 하는 것에 대한 불확실성을 피하기 위해서, 당신은 항상 원자단위로 억세스를 하는 특별한 데이터 타입을 사용할 수 있다: sig_atomic_t. 이 데이터타입을 읽기와 쓰기는 단일한 명령으로 발생한다는 것이 보증되므로 핸들러가 억세스의 "중간에" 실행될 방법이 없는 것이다.

sig_atomic_t 타입은 항상 정수 데이터 타입이지만, 그 데이터 타입이 몇 개의 비트로 구성되어있는지는 한가지로 정해진 것이 아니라 각각의 기계마다 다양하다.

데이터 타입 : sig__atomic__t

이것은 정수 데이터 타입이다. 이 타입의 오브젝트는 항상 자동적으로 억세스 된다.
실제로, 당신은 int와 int보다는 길지 않은 다른 정수형을 원소단위라고 가정할 수 있다. 당신은 또한 포인터형을 원소단위로 가정할 수 있다; 그것은 매우 편리하다. GNU C 라이브러리를 지원하는 모든 기계와 모든 POSIX 시스템상에서 그 두 개의 가정은 모두 사실이다.

 

21. 4. 7. 3 원소단위 사용 형태.

억세스의 어떤 형태는 억세스가 인터럽트 되는 것과 같은 문제들을 피한다. 예를 들어, 핸들러에 의해 설정되고, 때때로 메인 프로그램에 의해서 소거되고 테스트되는 어떤 플래그를 억세스 하는데 실제로 두 개의 명령(instructions)이 필요하다고 할지라도 항상 안전하다. 이것이 그렇게 보이도록 하기 위해서, 우리는 인터럽트 되어질 수 있는 모든 억세스를 고려해야만 하고, 인터럽트 되면 아무런 문제가 없음을 보여야 한다. 플래그를 테스트하는 도중에 발생한 인터럽트는 아무런 문제가 없는 정확한 값인 경우에, 0이 아닌 값으로 인식이 되거나 또는 테스트된 다음에 0이 아닌 값으로 되어질 것이기 때문에 아무런 문제가 없다.

플래그를 소거하는 도중에 인터럽트도 아무런 문제가 없는데, 플래그가 소거되기 전에 시그널이 발생한 것은, 그 값이 0으로 끝나거나, 아니면 0이 아닌 값으로 끝나고, 플래그가 소거된 후에 시그널이 발생한 것처럼 연속적인 사건들이 발생한다. 그 두 개의 경우 모두 코드가 처리되기만 하면, 플래그를 소거하는 도중에 발생한 시그널 또한 처리 할 수 있다. ( 이것은 비-원소단위의 사용이 언제 안전할 수 있는지를 당신에게 설명하기 위한 예제이다. )

때때로 당신은 다른 오브젝트를 사용해서 어떤 오브젝트의 사용을 막음으로써 그 오브젝트에 인터럽트 되지 않는 억세스를 보증할 수 있다, 그것의 형태는 원소단위가 확실할 것이다. 21. 4. 5절 [Merged Signals] 에서 예제참조.


21. 5 시그널에 의해 인터럽트된 기본동작 ( Primitives )

open 이나 read가 입/출력 디바이스에서 기다리는 것과 같은 입/출력 기본동작 동안에 시그널이 발생할 수도 있고 처리될 수도 있다. 만일 시그널 핸들러가 반환하면, 그 시스템은 의문을 갖는다: 다음에 무슨 일이 발생하지?

POSIX는 한가지 접근법을 정한다: 즉시 그 기본동작을 실패로 만든다. 이러한 종류의 실패를 위한 에러코드는 EINTR이다. 이것은 유연하지만, 보통은 불편하다. 전형적으로, POSIX 어플리케이션은 그 호출을 다시 할 목적으로 라이브러리 함수가 반환했을 때 EINTR인지 체크하는 시그널 핸들러를 사용한다. 종종 프로그래머들은 체크하는 것을 잊는다.

GNU 라이브러리는 매크로 TEMP_FAILURE_RETRY를 사용해서, 임시적인 실패 후에 다시 호출을 시도하도록 하는 편리한 방법을 제공한다.

매크로 : TEMP__FAILURE__RETRY (expression)

이 매크로는 일단 expression을 평가한다. 만일 그것이 실패이면 에러코드 EINTR을 보고하고, TEMP_FAILURE_RETRY는 그것을 다시 평가하고, 그것이 일시적인 실패가 아닐 때까지 계속 반복한다. TEMP_FAILURE_RETRY에 의해 반환된 값은 수행된 expression의 값이다.

BSD는 완전히 EINTR을 피하고 좀더 편리한 접근법을 제공한다: 그것을 실패로 만드는 대신에 인터럽트된 기본동작을 다시 시작한다. 만일 당신이 이 접근법을 선택한다면, 당신은 EINTR에 관심을 가질 필요가 없다.

GNU 라이브러리에서는 접근법을 선택할 수 있다. 만일 당신이 시그널 핸들러를 만드는 sigaction을 사용한다면, 당신은 핸들러가 어떻게 동작할지를 정할 수 있다. 만일 당신이 SA_RESTART 플래그를 지정하면, 핸들러부터의 반환은 어떤 기본동작을 다시 시작할 것이다; 그렇지 않으면, 핸들러로부터의 반환은 EINTR을 발생할 것이다. 21. 3. 5절 [Flags for Sigaction] 참조. 다른 방법은 siginterrupt 함수를 사용하는 것이다. 21. 9. 1절 [POSIX vs BSD] 참조.

당신이 한 특정한 핸들러에서 sigaction 이나 siginterrupt로 할 일을 정하지 않을 때, 그것은 디폴트 선택을 사용한다. GNU 라이브러리에서 디폴트 선택은 당신이 정의한 테스트 매크로에 의존한다. 만일 시그널이 발생되기 전에 _BSD_SOURCE 또는 _GNU_SOURCE로 정의하면, 디폴트는 기본동작을 다시 시작하는 것이다; 그렇지 않다면, 디폴트는 EINTR로 그들을 실패하게 만드는 것이다. ( 라이브러리는 signal 함수의 다양한 변형을 포함하고 있고, 당신이 사용한 테스트 매크로에 따라서 실제로 호출될 signal 함수가 결정된다. ) 1. 3. 4절 [Feature Test Macros] 참조. 위와 같은 문제에 영향을 받는 기본동작들은 close, fcntl(operation F_SETLK), open, read, recv, recvfrom, select, send, sendto, tcdrain, waitpid, wait, 그리고 write가 있다.

결코 재개(resumption)가 발생되지 않는 한가지 상황이 있다: read 와 write 와 같은 데이터-참조 함수가 데이터의 일부분만을 참조한 후에 시그널에 의해서 인터럽트 되었을 때. 이 경우, 그 함수는 부분적인 성공을 지적하기 위해서, 이미 참조된 바이트의 개수를 반환한다.

레코드-지향 디바이스 상에서는 두 개의 레코드를 read 하거나 write하려는 것에서 한 개로 read, write를 분리해버리는 것 과 같은 이상한 동작의 원인이 될 수도 있다. (데이터그램 소켓을 포함; 11. 9절 [Datagrams] 참조. ). 실제로는, 그와같은 디바이스 상에서는 데이터를 참조 중에 인터럽션이 발생할 수 없기 때문에 아무런 문제가 없다; 그와같은 디바이스들은 일단 데이터 참조가 시작되면 아무런 기다림이 없이, 한 버스트(burst)에 전체 레코드를 항상 참조한다.

 
**역자주 : 버스트(burst) : 중간에 어떤 이유들로 인해서 중단이 발생하지 않고, 한 묶음의 데이터를 한꺼번에 전달하는 방법을 의미함.


21. 6 시그널 발생시키기

하드웨어 트랩이나 인터럽트의 결과로서 발생되는 시그널을 제외하고, 당신의 프로그램에서 프로세스, 또는 다른 프로세스에게 명시적으로 시그널을 보낼 수 있다.

 

21. 6. 1 스스로에게 신호 보내기

프로세스는 raise 함수를 통해서 시그널을 스스로에게 보낼 수 있다. 이 함수는 `signal. h'에 선언되어 있다.

함수 : int raise (int signum)

raise 함수는 호출한 프로세스에게 시그널 signum을 보낸다. 성공하면 0을 반환하고 실패하면 0이 아닌 값을 반환한다. 실패하게 되는 유일한 이유는 signum의 값이 무효한 경우이다.

함수 : int gsignal (int signum)

gsignal 함수는 raise와 같은 일을 하지만 SVID와의 호환성을 위해서 제공된다.

raise 사용으로 한가지 편리한 점은 당신이 트랩 했던 시그널의 디폴트 동작을 재생할 수 있다는 것이다. 이를테면, 당신의 프로그램을 사용하는 사용자가 stop 시그널(SIGTSTP)을 보내기 위해서 SUSP 문자를 타이핑할 때, 당신은 멈추기 전에 어떤 내부적 데이터 버퍼들을 소거하기를 원한다고 가정하자. 당신은 다음처럼 이것을 설정할 수 있을 것이다.

#include <signal. h>
 
/* stop 시그널이 도착했을 때, 원래의 디폴트로 동작을 설정하고 소거 동작을 한 후에 그 시그널을 다시 보낸다. */
void
tstp_handler (int sig)
{
signal (SIGTSTP, SIG_DFL);
/* 여기서 소거 동작을 하라 */
. . .
raise (SIGTSTP);
}
 
/* 프로세스가 다시 계속될 때, 시그널 핸들러를 반환하라. */
 
void
cont_handler (int sig)
{
signal (SIGCONT, cont_handler);
signal (SIGTSTP, tstp_handler);
}
 
/* 프로그램을 초기화하는 동안에 양(both) 핸들러를 가능하게 하라 */
 
int
main (void)
{
signal (SIGCONT, cont_handler);
signal (SIGTSTP, tstp_handler);
. . .
}
 
이식성 노트: raise 는 ANSI C 위원회에 의해 만들어졌다. 오래된 시스템들은 그것을 지원하지 않을 것이기 때문에, 이식성을 위해서는 kill을 사용하라. 21. 6. 2절[Signaling Another Process] 참조.

 

21. 6. 2 다른 프로세스에게 시그널 보내기

kill 함수는 다른 프로세스에게 시그널을 보내기 위해서 사용될 수 있다. 함수의 명칭이 악의적이지만, 그것은 다른 프로세스를 종료시키는데 사용하기보다는 더 많은 것들을 위해서 사용될 수 있다. 다음의 경우, 당신이 프로세스들 사이에 시그널들을 보내기 원할 때 사용할 수 있다.

부모 프로세스가 작업을 수행하기 위해서 자식 프로세스를 시작한다. 아마도 자식 프로세스는 한정된 루프를 돌 것이고_자식 프로세스가 작업에서 더 이상 필요치 않을 때 종료한다.

한 프로세스가 그룹(group)의 일부로써 실행될 때, 에러나 다른 사건이 발생하면 그룹에 있는 다른 프로세스에게 신고하거나 종료할 필요가 있다.

두 개의 프로세스가 서로 작업하는 동안 동기(synchronize)할 필요가 있다.

이 절은 당신이 프로세스가 어떻게 작업하는지에 대해서 조금이나마 알 것이라고 가정한다. 이 주제에 대한 자세한 정보는 23장 [Child Process] 에 나와있다. kill 함수는 `signal. h'에 선언되어 있다.

함수 : int kill (pid_t pid, int signum)

kill 함수는 pid로 정해진 프로세스나 프로세스 그룹에게 시그널 signum을 보낸다. 21. 2절 [Standard Signals] 에 나와있는 시그널들뿐만 아니라, signum은 pid의 유효성을 체크하기 위해서 0의 값을 가질 수 있다. pid는 시그널을 받기 위한 프로세스나 프로세스 그룹을 지정한다.

pid > 0

pid는 프로세스이다.

pid == 0

같은 프로세스 그룹에 있는 모든 프로세스에게 시그널을 보내지만, 시그널을 보내는 프로세스 자체는 그 시그널을 받지 않는다.

pid < -1

-pid는 프로세스 그룹이다.

pid == -1

만일 그 프로세스에게 특권이 부여되어 있다면, 어떤 특별한 시스템 프로세스들을 제외한 모든 프로세스들에게 시그널을 보낸다. 그렇지 않다면, 같은 사용자 ID를 가진 모든 프로세스에게 시그널을 보낸다.
 
kill (getpid(), signum) 과 같은 호출로 프로세스는 자신에게 시그널 signum을 보낼 수 있다. 만일 kill이 자신에게 시그널을 보내도록 어떤 프로세스에 의해 사용되고, 그 시그널이 블록되지 않는다면, kill은 반환하기 전에 프로세스에게 적어도 한 개의 시그널( 시그널 signum 대신에 아직 미해결 상태로 남아있는 블록되지 않은 다른 시그널이 될 수도 있다. )을 배달한다.
 
kill로 부터의 반환값은, 만일 성공적으로 시그널이 보내질 수 있다면 0을 반환한다. 그렇지 않고, 아무런 시그널도 보낼 수 없다면 -1을 반환한다. 만일 pid를 여러 개의 프로세스에게 시그널을 보내도록 지정한다면, 그때 만일 kill이 그들에게 적어도 한 개의 시그널을 보낼 수 있다면 성공한 것이다. 당신이 그들 모두에게 시그널이 갔는지 또는 어떤 프로세스가 시그널을 얻었는지를 알 수 있는 방법은 없다.

다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EINVAL

signum 인수가 무효하거나 그 숫자를 지원하지 않는 시그널 번호를 사용했다.

EPERM

당신은 pid에 의해 지정된 프로세스나 프로세스 그룹 안의 어떤 프로세스들에게 시그널을 보내기 위한 특권을 갖고 있지 않다.

ESCRH pid

인수가 현존하고 있는 프로세스나 그룹으로 지정되지 않았다.

함수 : int killpg (int pgid, int signum)

이것은 kill과 유사하지만, 프로세스 그룹에게는 시그널을 보낼 수 없다. 이 함수는 BSD와의 호환성을 위해서 제공되었다; 이식성을 위해서는 kill을 사용하라.
kill 사용의 간단한 예로서, kill(getpid(), sig)은 raise(sig)와 같은 효과를 갖는다.

 

21. 6. 3 kill을 사용하기 위한 허가

어느 임의의 프로세스에게 시그널을 보내기 위해서 kill을 사용하는 것을 방지하기 위한 제한이 있다. 그것은 다른 사용자에게 소속되어 있는 프로세스를 제멋대로 죽이는 것과 같은 반사회적인 행동을 방지하기 위한 의도가 있다. kill을 자식과 부모 프로세스사이에 시그널을 주고 받기 위해 사용하는것과 같은 상황에서는, 보통 당신은 시그널을 보내기 위한 허가권을 갖고 있다. 그러나 자식 프로세스에서 setuid 프로그램이 실행될 때는 유일하게 제외된다; 만일 프로그램이 실제 UID를 유효 UID로 변경한다면, 당신은 시그널을 보내기 위한 허가권을 가지지 않을 수도 있다. su 프로그램을 이런 일을 한다.

프로세스가 다른 프로세스에게 시그널을 보내기 위한 허가권을 가지고 있는지 없는지의 여부는 두 개의 프로세스의 사용자 ID들에 의해 결정된다. 이 원칙은 25. 2절 [Process Personal] 에 자세하게 논의되고 있다.

일반적으로, 어떤 프로세스가 다른 프로세스에게 시그널을 보낼 수 있기 위해서는, 시그널을 보내는 프로세스가 특권이 부여된 사용자(`root'처럼)이거나 시그널을 보내는 프로세스의 실제 또는 유효 사용자 ID가 시그널을 받는 프로세스의 실제 또는 유효 사용자 ID와 매치되어야만 한다. 만일 시그널을 받는 프로세스가 프로세스 이미지 파일에서 set-user-ID 모드를 통해 유효 사용자 ID를 변경했다면, 프로세스 이미지 파일의 소유자가 현재 유효 사용자 ID 대신에 사용된다. 어떤 경우에, 만일 사용자 ID들이 매치되지 않는다 할지라도 부모 프로세스가 자식 프로세스에게 시그널을 보내는 것이 가능하고, 다른 경우에는 다른 제한들이 강요 될 것이다. SIGCONT 시그널은 특별한 경우이다. 그것은 만일 시그널을 보내는 쪽이 시그널을 받는 쪽과 같은 세션에 있다면, 사용자 ID들에 상관없이 시그널을 보낼 수 있다.

 

21. 6. 4 통신을 위해서 kill을 사용하기

다음은 프로세스간 통신을 위해서 어떻게 시그널들을 사용할 수 있는지 보여주는 조금 긴 예제 프로그램이다. SIGUSR1 과 SIGUSR2가 프로세스 간 통신을 지원하지 위하여 제공된 것이다. 그 시그널들은 기본적으로 치명적이기 때문에, 그 시그널들을 받을 것으로 가정되는 프로세스는 signal 이나 sigaction을 통해서 그들을 트랩 해야만 한다.

다음의 예제는, 부모 프로세스가 fork로 자식 프로세스를 생성한 다음 자식 프로세스가 초기화를 수행할 때까지 기다린다. 자식 프로세스는 준비가 되었음을 알리기위해서, kill 함수를 사용해서 SIGUSR1 시그널을 보낸다.

#include <signal. h>
#include <stdio. h>
#include <sys/types. h>
#include <unistd. h>
 
/* SIGUSR1 시그널이 도착할 때, 이 변수를 설정하라 */
volatile sig_atomic_t usr_interrupt = 0;
 
void
synch_signal (int sig)
{
usr_interrupt = 1;
}
 
/* 자식 프로세스가 이 함수를 실행한다. */
void
child_function (void)
{
/* 초기화를 수행하라 */
printf ("I'm here!!! My pid is %d. \n", (int) getpid ());
 
/* 초기화를 수행했음을 부모에게 알리자 */
kill (getppid (), SIGUSR1);
 
/* 실행을 계속한다 */
puts ("Bye, now. . . . ");
exit (0);
}
 
int
main (void)
{
struct sigaction usr_action;
sigset_t block_mask;
pid_t child_id;
 
/* 시그널 핸들러를 만들어라. */
sigfillset (&block_mask);
usr_action. sa_handler = synch_signal;
usr_action. sa_mask = block_mask;
usr_action. sa_flags = 0;
sigaction (SIGUSR1, &usr_action, NULL);
 
/* 자식 프로세스를 만들어라 */
child_id = fork ();
if (child_id == 0)
child_function (); /* 반환하지 말아라. */
 
/* 자식 프로세스가 시그널을 보낼 때까지 기다리자. */
while (!usr_interrupt)
;
 
/* 이제 실행을 계속한다. */
puts ("That's all, folks!");
 
return 0;
}

위의 예제는 busy wait(적당한 말이 없어서. . )을 사용하는데, 그것은 다른 프로그램에서 사용할 수 있도록 CPU 사이클을 기다려야하기 때문에 좋지 않다. 시그널이 도착할 때까지 기다리도록 시스템에게 부탁하는 것이 더 좋다. 21. 8절 [Waiting for a signal] 에 있는 예제를 참조하라.


21. 7 시그널 블록하기

시그널 블록하기는 운영체제에게 그 시그널을 붙잡아서 나중에 배달하도록 알리는 것을 의미한다. 일반적으로, 프로그램에서는 SIG_IGN을 사용해서, 시그널의 동작을 무시하는 것으로 설정할 망정, 불명확하게 시그널들을 블록하지 않는다. 하지만 시그널 블록킹(blocking)은 민감한 오퍼레이션들이 인터럽트 되는 것을 막기 위해서 시그널들을 블록하는데 사용된다.

시그널들 때문에 핸들러에 의해 수정되었던 전역 변수들을 갱신하는 동안 시그널들을 블록하기 위해서 sigprocmask 함수를 사용 할 수 있다.

특정한 핸들러가 실행되는 동안 어떤 시그널들을 블록하도록 sigaction 함수호출에서 sa_mask를 설정할 수 있다. 이 방법으로, 시그널 핸들러는 시그널들에 의해서 그 자체가 인터럽트 됨이 없이 실행될 수 있다.

 

21. 7. 1 왜 시그널 블록킹 ( Blocking ) 이 유용한가

sigprocmask을 사용해서 임시적으로 시그널 블록하기는 당신의 프로그램에서 임계부분(critical parts)이 실행되는 동안에 발생할지도 모를 인터럽트를 막기 위한 방법으로 제공된다. 만일 시그널들이 프로그램의 그 부분(critical parts)에 도달한다면, 당신이 그들의 블록을 해제한 후에, 나중에 배달 되어진다. 이것의 유용한 사용예는 프로그램의 나머지와 시그널 핸들러 사이에 데이터를 분배하는데 사용하는 것이다. 만일 데이터의 타입이 sig_atomic_t( 21. 4. 7절 [Atomic Data Access] 참조. )가 아니라면 시그널 핸들러는 프로그램의 나머지가 데이터의 읽기와 쓰기를 완전히 끝냈을 때 실행될 수 있다. 이것은 혼란된 결과를 초래할 것이다.

신뢰 가능한 프로그램을 만들기 위해서, 프로그램의 나머지가 데이터를 시험하거나 갱신하는 동안에 시그널 핸들러가 실행되는 것을 막을 수 있다_프로그램의 나머지가 실행되는 동안 발생할 여지가 있으며, 그 데이터를 건드릴 위험이 있는 적당한 시그널들을 블록함으로 해서. 만일 어떤 시그널이 도착하지 않았을 때, 당신이 어떤 동작을 수행하기를 바란다면 시그널 블록킹은 필요하다. 그 시그널을 위한 핸들러가 sig_atomic_t 타입의 플래그를 설정한다고 가정하다; 당신은 그 플래그를 시험하고 만일 그 플래그가 설정되지 않았다면 어떤 동작을 수행하도록 하고 싶어한다. 하지만 이것은 신뢰할 수 없다. 만일 그 시그널이 아직 중요한 동작은 수행하기 전이고, 플래그는 테스트 한 직후에 시그널이 배달된다고 가정하면, 그 프로그램은 시그널이 도착할지라도 그 동작을 수행 할 것이다.

어떤 시그널이 도착했는지의 여부를 확인하는 유일한 신뢰 가능한 방법은 시그널이 블록되어 있을 동안 테스트하는 것이다.

 

21. 7. 2 시그널 설정

시그널을 블록 킹하는 함수들 모두는 무슨 시그널들이 영향을 받게되는지를 정하는 데이터 구조체를 사용한다. 그리고, 두 개의 단계, 즉, 시그널을 만들기와 시그널을 라이브러리 함수에 인수로써 사용하기를 포함한다.

그들은 헤더파일 `signal. h'에 선언되어 있다.

데이터 타입 : sigset__t

sigset_t 데이터 타입은 시그널 셋(set)을 표현하기 위해서 사용된다. 내부적으로, 정수나 구조체형으로 이행될 것이다. 이식성을 위해서, sigset_t 오브젝트에서 정보를 초기화하고, 변경하고, 추출하는 역할을 하는, 이 절에서 설명된 함수들을 사용하라_그들을 직접적으로 다루려고 시도하지 마라.

시그널 셋(set)을 초기화하기 위한 두 가지 방법이 있다. 하나는 처음에 sigemptyset을 사용하여 비어있게 해놓은 다음, 개별적으로 시그널을 하나씩 더한다. 아니면, sigfillset을 사용하여 완전히 채운다음, 개별적으로 정해진 시그널들을 하나씩 지운다.

당신이 어떤 식으로든 그것을 사용하기 전에 그 두 개의 함수중 하나로써 시그널 셋(set)을 초기화해야만 한다. 모든 시그널들을 명시적으로 설정하려 시도하지 말아라, 왜냐하면, sigset_t 오브젝트는 초기화될 필요가 있는 어떤 다른 정보(버전 필드와 같은)를 포함하고 있을 것이기 때문이다. (더하자면, 당신이 알고 있는 것 외의 시그널은, 시스템이 발생시키지 않을 것이라는 가정을 당신의 프로그램 안에서 하는 것은 현명하지 못하다. )

함수 : int sigemptyset (sigset_t *set)

이 함수는 정의된 모든 시그널을 포함하도록 시그널 셋(set)을 set으로 초기화한다. 이 함수는 항상 0을 반환한다.

함수 : int sigfillset (sigset_t *set)

이 함수는 정의된 모든 시그널을 포함하도록 set으로 시그널 셋을 정한다. 반환값은 0이다.

함수 : int sigaddset (sigset_t *set, int signum)

이 함수는 시그널 셋에 시그널 signum을 더한다. sigaddset이 하는 모든 것은 셋(set)을 갱신하는 것이다; 어느 시그널을 블록하지 않거나 또는 블록을 해제하지 않거나 한다. 반환값은 성공하면 0이고 실패하면 -1이다.
다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

EINVAL : signum 인수로 무효한 시그널을 지정하였다.

함수 : int sigdelset (sigset_t *set, int signum)

이 함수는 시그널 셋 set으로부터 시그널 signum을 제거한다. sigdelset이 하는 모든 것은 셋(set)을 갱신하는 것이다; 그것은 시그널을 블록하지 않거나 블록을 해제하지 않거나 한다. 반환값과 에러상황은 sigaddset과 같다.

마지막으로, 시그널 셋(set)안에 어떤 시그널들이 있는지를 테스트하기 위한 함수의 설명이다.

함수 : int sigismember (const sigset_t *set, int signum)

sigismember 함수는 시그널 signum이 시그널 집합 set의 멤버인지의 여부를 테스트하는 함수이다. 만일 그 시그널이 집합 안에 있으면 1을 반환하고, 그렇지 않으면 0을 반환하고, 에러가 발생하면 -1을 반환한다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.
EINVAL signum 인수에 무효한 시그널이 지정되었다.

 

21. 7. 3 프로세스 시그널 마스크

현재 블록되어 있는 시그널들의 모음(collection)을 시그널 마스크라고 부른다. 각 프로세스는 자신 소유의 시그널 마스크를 갖고 있다. 당신이 새로운 프로세스를 만들 때(23. 4절 [Creating a Process] 참조) 그것은 부모의 마스크를 상속받는다. 당신은 시그널 마스크를 갱신하여 유연성 있게 시그널들을 블록하거나 해제할 수 있다.

sigprocmask 함수의 프로토타입은 `signal. h'에 있다.

함수 : int sigprocmask (int how, const sigset_t *set, sigset_t *oldset)

sigprocmask 함수는 호출된 프로세스의 시그널 마스크를 시험하거나 갱신하는데 사용된다. how 인수는 시그널 마스크를 어떻게 변경할지 정하는 인수로써, 다음 값들 중에 하나를 사용해야만 한다.
 
SIG_BLOCK set에 있는 시그널들을 블록하라. 현존하는 마스크에 그들을 더하라. 즉, 새로운 마스크는 현존하는 마스크와 set의 합집합이다.
 
SIG_UNBLOCK set에 있는 시그널들의 블록을 해제하라. 현존하는 마스크에서 그들을 제거하라.
SIG_SETMASK 마스크로 set을 사용하라; 마스크의 전의 값은 무시하라.
 
마지막 인수 oldset은 예전 프로세스 시그널 마스크에 대한 정보를 반환하는데 사용된다. 만일 당신이 예전 프로세스 시그널 마스크에 대한 정보를 살펴보지 않고 마스크를 변경하기 원한다면, oldset 인수에 널 포인터를 사용하면 된다. 유사하게, 만일 당신이 현존하는 마스크를 변경하지 않고, 단지 마스크 안에 무엇이 있는지 알기를 원한다면, set 인수에 널 포인터를 사용하면 된다. ( 이 경우, how 인수는 아무런 의미가 없다. )
 
oldset 인수는 나중에 예전 프로세스 시그널 마스크를 반환하기 위해 그 시그널 마스크를 기억하는데 종종 사용된다. ( fork 와 exec의 호출로 시그널 마스크가 상속된 후, 당신은 당신의 프로그램의 실행을 시작할 때 그 안에 무슨 내용이 있는지 예언할 수 없다. )
 
호출된 sigprocmask가 어느 미해결인 상태의 시그널의 블록을 해제하게 된다면, 그들 시그널들 중 적어도 하나는 sigprocmask가 반환하기 전에 프로세스에게 배달된다. 배달된 미해결 시그널의 순서는 정해지지 않았지만, 당신이 한꺼번에 여러 개의 시그널들의 블록을 해제하도록 다중의 sigprocmask을 사용함으로써 명시적으로 순서를 제어할 수 있다.
 
sigprocmask함수는 성공하면 0을 반환하고, 실패하면 -1을 반환한다.
다음의 errno는 이 함수를 위해 정의된 에러상황이다.
EINVAL : how 인수가 무효하다.

당신은 SIGKILL 과 SIGSTOP 시그널들을 블록할 수 없지만, 만일 시그널 셋이 그들을 포함한다면, sigprocmask는 에러 상황을 보고하는 대신에 그들을 단지 무시한다. 기억하라, SIGFPE와 같은 프로그램 에러 시그널들을 블록하는 것은 실제 프로그램 에러에 의해 발생된 시그널로 인해 바람직하지 못한 결과를 초래한다. (raise 나 kill에 의해 만들어진 시그널들은 제외하고) 이것은 시그널이 다시 블록이 해제되었을 때, 그 지점에서 실행을 계속하지 못할 정도로 프로그램이 파괴되었기 때문이다. 21. 2. 1절 [Program Erroe Signals] 참조.

 

21. 7. 4 시그널의 배달 여부를 테스트하기 위한 블럭킹

다음은 간단한 예제이다. SIGALRM 시그널이 도착할 때마다 플래그를 설정하는 핸들러를 만들고, 메인 프로그램에서는 시간마다 이 플래그를 체크하고 그것을 재설정한다고 가정하자. 당신은 sigprocmask를 호출해서 코드의 임계부분을 보호함으로써 그 동안 도착한 부가적인 SIGALRM 시그널을 막을 수 있다.

/* 이 변수는 SIGALRM 시그널 핸들러에 의해 설정된다. */

int
main (void)
{
sigset_t block_alarm;
. . .
 
/* 시그널 마스크를 초기화한다. */
sigemptyset (&block_alarm);
sigaddset (&block_alarm, SIGALRM);
 
while (1) {
/* 시그널이 도착했는지를 체크하라; 만일 도착했다면, 플래그를 재설정하라. */
sigprocmask (SIG_BLOCK, &block_alarm, NULL);
if (flag) {
actions-if-not-arrived
flag = 0;
}
sigprocmask (SIG_UNBLOCK, &block_alarm, NULL);
. . .
}
}

 

21. 7. 5 핸들러를 위하여 블록된 시그널

시그널 핸들러가 호출되었을 때, 당신은 보통 그 시그널 핸들러가 다른 시그널에 의해 블록됨이 없이 끝나기를 원한다. 그 핸들러가 시작된 순간부터 끝나는 순간까지, 당신은 핸들러의 데이터를 오염시키거나 혼란시킬지도 모르는 시그널을 블록해야만 한다.

한 시그널에 의해 핸들러 함수가 호출되었을 때, 핸들러가 실행되는 동안 그 시그널은 자동적으로 블록된다 ( 다른 시그널과 함께 그 시그널은 이미 프로세스의 시그널 마스크에 존재하게된다. ) 만일 예를 들어 당신이 SIGTSTP를 위한 핸들러를 준비했을 때, 그 시그널이 도착하면 핸들러는 핸들러가 실행되는 동안 기다리도록 하여 나중에 SIGTSTP 시그널을 다시 발생시킨다.

그렇지만, 디폴트로, 다른 종류의 시그널들은 블록되지 않았다; 그들은 핸들러가 실행되는 동안 발생할 수도 있다. 핸들러가 실행되는 동안 다른 종류의 시그널을 블록하기 위한 좋은 방법은 sigaction 구조체의 sa_mask 멤버를 사용하는 것이다. 다음은 그에 대한 예제이다.

#include <signal. h>
#include <stddef. h>
 
void catch_stop ();
 
void
install_handler (void)
{
struct sigaction setup_action;
sigset_t block_mask;
sigemptyset (&block_mask);
/* 핸들러가 실행되는 동안 다른 터미널-발생 시그널들을 블록하라. */
sigaddset (&block_mask, SIGINT);
sigaddset (&block_mask, SIGQUIT);
setup_action. sa_handler = catch_stop;
setup_action. sa_mask = block_mask;
setup_action. sa_flags = 0;
sigaction (SIGTSTP, &setup_action, NULL);
}

핸들러 코드 안에서 명시적으로 다른 시그널들을 블록하는 것보다는 더 신뢰 가능하다. 만일 당신이 핸들러 안에서 명시적으로 시그널들을 블록한다면, 아직 당신이 그들을 블록하지 않았을, 핸들러 시작 초기의 짧은 간격동안에 발생된 시그널로 인한 문제는 피할 수가 없다.

이 메커니즘을 사용하여 프로세스의 현재 마스크로부터 시그널들을 제거 할 수 없다. 그렇지만, 핸들러 함수에서 sigprocmask를 호출하여, 당신이 원하는 시그널을 블록하거나 해제하도록 만들 수 있다. 어쨌든, 핸들러 함수가 반환할 때, 시스템은 핸들러 함수가 진입하기 전으로 마스크를 반환한다.

 

21. 7. 6 미해결 시그널 체크하기

당신은 sigpending 을 호출하여 어느 시점에서 미해결 상태인 시그널들을 발견해낼 수 있다. 이 함수는 `signal. h'에 선언되어 있다.

함수 : int sigpending (sigset_t *set)

sigpending 함수는 set에 미해결인 시그널에 대한 정보를 저장한다. 만일 미해결 상태의 시그널이 있다면, 그 시그널은 반환된 set의 멤버이다. (당신은 sigismember 을 사용해서 특정한 시그널이 이 set의 멤버인지 테스트 할 수 있다; 21. 7. 2절 [Signal Sets] 참조. ) 성공하면 반환값은 0이고 실패하면 -1이다.

시그널이 미해결 상태인지를 테스트하는 것은 자주 유용하지는 않다. 시그널이 블록되지 않았을 때 테스트하는 것은 좋지 않다. 다음의 예제를 살펴보자.

#include <signal. h>
#include <stddef. h>
 
sigset_t base_mask, waiting_mask;
 
sigemptyset (&base_mask);
sigaddset (&base_mask, SIGINT);
sigaddset (&base_mask, SIGTSTP);
 
/* 다른 프로세싱을 하는 동안에 사용자 인터럽트를 블록하라. */
sigprocmask (SIG_SETMASK, &base_mask, NULL);
. . .
 
/* 그 후에, 어느 시그널이 미해결인지를 체크하라. */
sigpending (&waiting_mask);
if (sigismember (&waiting_mask, SIGINT)) {
/* 사용자가 프로세스를 죽이기기를 시도한다 */
} else if (sigismember (&waiting_mask, SIGTSTP)) {
/* 사용자가 프로세스를 멈추기를 시도한다 */
}

당신의 프로세스를 위하여 미해결상태인 특정한 시그널이 있다면 그 동안에 도착한 같은 종류의 부가적인 시그널들은 버려질 것임을 기억하라. 예를 들어, 만일 SIGINT 시그널이 미해결 상태일 때 다른 SIGINT 시그널이 도착하면, 당신의 프로그램은 이 시그널의 블록을 해제할 때 오직 한 개의 SIGINT 시그널로 처리할 것이다.

 
이식성 노트 : sigpending 함수는 POSIX. 1에 새로이 추가되었다. 오래된 시스템들은 이와 동등한 함수가 없다.

 

21. 7. 7 나중에 동작하도록 시그널을 기억하기

라이브러리 함수를 사용해서 시그널을 블록하는 대신에, 당신은 당신이 블록을 "해제"할 때, 나중에 테스트 되도록 플래그를 설정하는 핸들러를 만들어서 거의 같은 결과를 얻을 수 있다.

다음의 예제를 살펴보라.

/* 만일 이 플래그가 0이 아니라면, 즉시 그 시그널을 처리하지 말아라. */
volatile sig_atomic_t signal_pending;
 
/* 이것은 시그널이 도착했고 처리되지 않았으면 0이 아니다. */
volatilesig_atomic_t defer_signal;
 
void
handler (int signum)
{
if (defer_signal)
signal_pending = signum;
else
. . . /* "실제로" 그 시그널을 처리한다. */
}
 
. . .
 
void
update_mumble (int frob)
{
/* 시그널이 즉각적인 효력을 발휘하는 것을 막아라. */
defer_signal++;
/* 인터럽션에 대한 걱정 없이, 이제 mumble을 갱신한다. */
mumble. a = 1;
mumble. b = hack ();
mumble. c = frob;
/* 우리는 mumble을 갱신하였다. 들어와 있는 어느 시그널을 처리하라. */
defer_signal--;
if (defer_signal == 0 && signal_pending != 0)
raise (signal_pending);
}

도착한 특정한 시그널이_미해결인_어떻게 signal에 저장되었는지에 주목하라. 그와같은 방법으로, 우리는 아직 해결할 형편이 되지 않은 시그널의 다양한 종류를 처리할 수 있다.

우리는 defer_signal을 증가시키고 감소시켜서 중첩된 임계 구역(critical sections)을 적당히 작업하게 한다; 그래서, 만일 signal_pending 과 함께 호출되었던 update_mumble 의 값이 이미 0이 아니라면, 시그널들은 update_mumble안에서는 연기되지 않고, 오직 caller 내부에서만 연기된다. 이것은 defer_signal 이 여전히 0이 아닐 때, 왜 signal_pending을 체크하지 않는지에 대한 이유가 된다.

defer_signal 의 증가와 감소는 한 개의 명령보다는 많은 명령이 요구된다; 그러므로 중간에 시그널이 발생하는 것이 가능하다. 그러나 이것은 아무런 문제도 야기하지 않는다. 만일 증가나 감소를 시작하기 전에 발생했던 시그널과 동등한 그 시그널이 증가나 감소 전에 그 값을 보기 위해서 충분히 많이 발생된 것이라면, 이 경우 아무런 문제없이 작업한다.

signal_pending 을 테스트하기 전에 defer_signal을 증가시키는 것은 굉장히 중요하다, 왜냐하면 이것은 민감한 버그를 피하게 하기 때문이다. 만일 우리가 그와같은 일들을 다른 순서로 한다면 이것은 다음과 같다.

if (defer_signal == 1 && signal_pending != 0)
raise (signal_pending);
defer_singal--;

위의 경우 if 구문과 감소사이에 도착된 시그널은 불명확한 시간동안은 잃어버리게 된다. 핸들러는 완전하게 defer_signal을 설정하였지만, 프로그램은 이미 이 변수를 테스트해버렸고, 다시는 변수를 테스트하지 않을 것이다.

그와같은 버그들을 타이밍 에러라고 부른다. 그들은 희귀하게 발생하고 재생시키는데는 굉장히 중요하기 때문에 아주 나쁜 버그이다. 당신은 재생 가능한 버그를 발견하는 것처럼 디버거로 그들을 발견할거라고 예상하지 마라. 그렇기 때문에 그러한 버그를 피하기 위해서는 특별히 주의할 가치가 있다.

( 당신은 이러한 순서로 코드를 기록하고 싶은 유혹을 받지 말아라, defer_signal 이 카운터(counter)로써 사용된다면 signal_pending 과 함께 테스트되어야만 한다. 후에, 0에 대한 테스트는 1에 대한 테스트보다는 깨끗하다. 그러나 만일 당신이 defer_signal을 카운터로써 사용하지 않고, 0과 1의 값만 그것에 주어진다면, 순서는 간단하게 보여질 것이다. 이것은 defer_signal을 카운터로써 사용하는 것보다 더한 이득을 갖는다: 그것은 당신이 잘못된 순서로 코드를 기록하고 민감한 버그를 만들어낼 가능성을 감소시킬 것이다. )


21. 8 시그널을 위한 기다림

당신의 프로그램이 외부 사건에 의해서 조종되거나, 동기화를 위해서 시그널을 사용한다면, 그때 그 프로그램은 시그널이 도착할 때까지 기다릴 수밖에 없다.

 

21. 8. 1 pause 사용하기

시그널이 도착할 때까지 기다리기 위한 간단한 방법은 pause 를 호출하는 것이다. 당신이 그것을 사용하기 전에 다음절에 있는, 그것을 사용함으로 써 얻게되는 불리한 점을 보아라.

함수 : int pause ()

pause 함수는 핸들러 함수를 실행하거나, 또는 프로세스를 종료시키는 시그널이 발생할 때까지 프로그램의 실행을 잠시 멈추는 역할을 하는 함수이다.
 
만일 그 시그널이 핸들러 함수가 실행되도록 하는 원인이 된다면, pause는 반환한다. 이것은 비성공적인 반환으로 간주한다. (왜냐면, "성공적"인 행동은 영원히 프로그램을 멈추도록 할 것이기 때문이다. ) 그렇기 때문에 -1을 반환한다. 심지어 당신이 시스템 핸들러가 반환할 때 다른 기본동작(primitives)을 재 시작하도록 정한다고 해도 ( 21. 5절 [Interrupted Primitives] 참조. ), 이것은 pause에 아무런 영향을 미치지 않는다; 그것은 시그널이 처리될 때 항상 실패한다.
 
다음의 errno는 이 함수를 위해 정의된 에러 상황이다.
EINTR : 그 함수가 시그널의 배달로 인해서 인터럽트 되었다.
 
만일 그 시그널의 프로그램 종료의 원인이 된다면, pause는 반환하지 않는다(명백하게). pause 함수는 `unistd. h'에 선언되어 있다.  

 

21. 8. 2 pause 사용의 문제들

pause의 간단함은 프로그램을 이상하게 중단(hang) 시킬 수도 있는 심각한 타이밍 에러들을 숨길수도 있다. 만일 당신의 프로그램에서, 실제 작업이 시그널 핸들러에 의해서 수행되고, "메일 프로그램"은 pause는 호출하지만 아무런 일을 하지 않을 때는 안전하다. 시그널이 배달될 때마다, 핸들러는 해야할 작업을 하고, 다음에 반환한다, 그래서 프로그램의 메인 루프는 다시 pause를 호출 할 수 있다.

한 개 이상의 시그널이 도착하기를 기다렸다가 실제작업을 재개하기 위해서 pause를 사용하는 것은 안전할 수 없다. 당신이 플래그를 설정하는 것으로 시그널 핸들러를 조정한다고 할지라도, 당신은 여전히 pause함수를 믿을 수 없다. 다음에 이 문제에 대한 예제가 있다.

/* usr_interrupt는 시그널 핸들러에 의해 설정된다. */
if (!usr_interrupt)
pause ();
 
/* 시그널이 도착했을 때 작업하라. */
. . .

이것은 버그를 갖고 있다: 변수 usr_interrupt가 체크된 후, 하지만 pause가 호출되기 전에 시그널이 도착할 수 있다. 만일 앞으로 아무런 시그널이 도착하지 않으면, 프로세스는 결코 다시는 재개될 수 없다. puase를 사용하는 대신에 루프 안에서 sleep를 사용해서 오랜 기다림에 상위(upper) 제한을 가할 수 있다. (sleep에 대한 상세한 정보는 17. 4절 [Sleeping] 를 참조하라. ) 다음의 예제를 보자.

/* usr_interrupt는 시그널 핸들러에 의해 설정된다. */
while (!usr_interrupt)
sleep (1);
 
/* 시그널이 도착할 때 작업하라. */
. . .

어떤 목적으로도, 이것은 사용하기에 충분하다. 조금 더 복잡하기는 하지만, sigsuspend를 사용해서도 특정한 핸들러가 실행되는 동안 확실하게 기다릴 수 있다.

 

21. 8. 3 sigsuspend 사용하기

시그널이 도착하기를 기다리는 깨끗하고 신뢰 가능하다하다 방법은 그것을 블록하고 sigsuspend를 사용하는 것이다. 루프 안에서 사용된 sigsuspend는, 다른 종류의 시그널들이 그들의 핸들러에 의해 처리되는 동안, 어떤 종류의 시그널을 위해서 기다릴 수 있다.

함수 : int sigsuspend (const sigset_t *set)

이 함수는 set으로 프로세스의 시그널 마스크를 대체하고, 프로세스를 종료시키거나, 시그널 처리 함수를 호출하는 동작을 하는 시그널이 배달될 때까지 프로세스를 중지시킨다. 즉, 프로그램은 set의 멤버가 아닌 시그널중의 하나가 도착할 때까지 중지된다.
 
만일 어떤 프로세스가 핸들러 함수를 호출하는 시그널의 배달로 인해서 재개되면, 그 핸들러 함수는 반환하고, sigsuspend 또한 반환한다. 마스크는 sigsuspend가 기다리고 있는 동안만 set으로 유지된다. sigsuspend 함수는 반환할 때 항상 전의 시그널 마스크를 반환한다. 반환값과 에러상황은 pause와 같다.
 
sigsuspend를 사용해서, 앞절에서 나온 pause와 sleep를 완전하게 대체할 수 있다.
sigset_t mask, oldmask;
. . .
/* 일시적으로 블록할 시그널들의 마스크를 준비하라. */
sigemptyset (&mask);
sigaddset (&mask, SIGUSR1);
 
. . .
 
/* 시그널이 도착하기를 기다려라. */
sigprocmask (SIG_BLOCK, &mask, &oldmask);
while (!usr_interrupt)
sigsuspend (&oldmask);
sigprocmask (SIG_UNBLOCK, &mask, NULL);

코드의 마지막 부분은 약간 교묘하다. 이것의 핵심은 sigsuspend가 반환 할 때, 프로세스가 원래 가졌던 시그널 마스크의 값으로 재설정하는 것이다. 이 경우, SIGUSR1 시그널이 다시 블록되어진다. sigprocmask의 두 번째 호출은 이 시그널의 블록을 명백하게 해제할 필요가 있다.

다른 포인트 : 오직 하나의 SIGUSR1 시그널을 기다리는 그 프로그램에서 왜 while 루프가 필요한지 의아해 할지 모른다. 그 대답은, sigsuspend에 주어지는 마스크가, 예를 들어, 작업 제어 시그널처럼 다른 종류의 시그널이 배달됨으로 인해서 구동되어질 프로세스를 허가한다는 것이다. 만일 usr_interrput를 설정하지 않은 시그널에 의해 프로세스가 재개된다면, 그것은 단지 "올바른" 종류의 시그널이 발생할 때까지 다시 중지된다. 이 테크닉은 준비작업에 더 낳은 라인이 필요하지만, 당신이 시그널에 대한 정확한 기다림을 위해서는 필요하다. 실제로 기다림을 위한 코드는 단지 4줄뿐이다.


21. 9 BSD 시그널 핸들링

이 절은 BSD 유닉스에서 온 시그널 핸들링 함수들에 대해서 설명한다. 이들 도구들은 그들의 시대에서는 진보적이였지만; 오늘날은 굉장히 시대에 뒤떨어진 것이고, 오직 BSD와의 호환성을 위해서 제공되고 있다.

 

21. 9. 1 POSIX 와 BSD 시그널 기능들

POSIX 시그널 처리 기능들은 BSD 기능들로부터 나온 것이기 때문에 BSD와 POSIX 시그널 처리 기능들 사이에는 많은 유사성이 있다. 충돌을 피하기 위해서 모든 함수들이 서로 다른 이름을 갖고 있다는 것을 제외하고, 둘 사이에는 주요한 차이들이 있다.

BSD 유닉스는 sigset_t 오브젝트로 시그널 마스크를 나타내는 것이 아니라 int 비트마스크로써 시그널 마스크를 표현한다.

BSD 기능들은 인터럽트된 기본동작(primitive)을 실패하게 할 것인지 재개할 것인지의 여부에 대해서 다른 디폴트를 사용한다. POSIX 기능은 당신이 그들을 재개하도록 정할지라도 시스템 호출이 실패하게 만들고, BSD 기능들은, 당신이 그들을 실패하도록 정했을지라도 시스템 호출은 그것을 재개하도록 만드는 것이다. 21. 5절 [Interrupted Primitives] 참조.

BSD 유닉스는 시그널 스택의 구상을 갖는다. 이것은 보통의 실행 스택대신에, 시그널 핸들러 함수들의 실행동안에 사용되는 대체스택 (alternate stack)이다.

BSD 기능들은 `signal. h'에 선언되어 있다.


21. 10 핸들러 함수를 만들기 위한 BSD 함수

데이터타입 : struct sigvec

이 데이터 타입은 struct sigaction과 동등한 것이다( 21. 3. 2절 [Advanced Signal Handling] 참조); 이것은 sigvec함수에서 시그널 동작을 지정하기 위해서 사용된다. 그것은 다음과 같은 멤버들을 포함하고 있다.

sighandler_t sv_handler

이것은 핸들러 함수이다.

int sv_mask

이것은 핸들러 함수가 호출되어있을 동안에 블록될 부가적인 시그널들의 마스크이다.

int sv_flags

이것은 시그널의 동작에 영향을 미치는 다양한 플래그를 정하는데 사용되는 비트마스크이다. 당신은 sv_onstack로서 이 필드를 참조할 수 있다.

그들 기호 상수들은 sigvec 구조체의 sv_flags 를 위해 제공되는 값들로 사용될 수 있다. 이 필드는 비트마스크 값으로써, 당신이 관심을 갖는 플래그들을 비트별-OR를 사용해서 결합할 수 있다.

매크로 : int SV__ONSTACK

만일 이 비트가 구조체 sigvec의 sv_flags에서 설정되면, 그것은 시그널이 배달되었을 때 시그널 스택을 사용하는 것을 의미한다.

매크로 : int SV__INTERRUPT

만일 이 비트가 sigvec 구조체의 sv_flags안에서 설정되면, 이러한 종류의 시그널에 의해 인터럽트된 시스템 호출은 핸들러가 반환해도 재시작 되지 않을 것임을 의미한다; 대신에 시스템 호출은 EINTR 에러 상황으로 반환할 것이다. 21. 5절 [Interrupted Primitives] 참조.

매크로 : int SV__RESETHAND

만일 이 비트가 sigvec 구조체의 sv_flags에서 설정되면, 시그널을 받았을 때, SIG_DFL로 원래의 시그널을 위한 동작으로 재설정하는 것을 의미한다.

함수 : int sigvec (int signum, const struct sigvec *action, struct sigvec *old_action)

이 함수는 sigaction (21. 3. 2절 [Acvanced Signal Handling] 참조. )과 같다; 그것은 시그널 signum에 대한 동작을 action으로 하고, old_action에 그 시그널의 예전 동작에 대한 정보를 반환한다.

함수 : int siginterrupt (int signum, int failflag)

이 함수는 어떤 기본동작이 시그널 signum에 의해 인터럽트 되었을 때, 사용할 접근법을 지정한다. 만일 failflag가 false이면, 시그널 signum은 기본동작을 다시 시작한다. 만일 failflag가 true이면, 처리되는 signum은 에러코드 EINTR로 그들 기본동작을 실패하게 한다. 21. 5절 [Interrupted Primitives] 참조.

 

21. 10. 1 블록된 시그널을 위한 BSD 함수들

매크로 : int sigmask (int signum)

이 매크로는 시그널 signum을 위한 비트를 가진 시그널 마스크를 반환한다. 당신은 여러 개의 시그널을 정해주기 위해서 비트별-OR 연산자를 사용할 수 있다. 다음처럼.
(sigmask (SIGTSTP) | sigmask (SIGSTOP)
| sigmask (SIGTTIN) | sigmask (SIGTTOU))
 
이것은 모든 작업-제어 시그널 중 stop 시그널들을 포함하는 마스크를 지정한다.

함수 : int sigblock (int mask)

이 함수는 how 인수를 SIG_BLOCK로 가진, sigprocmask( 21. 7. 3절 [Process Signal Mask] 참조)과 같다: 그것은 호출된 프로세스의 블록된 시그널의 집합에, mask에 의해 정해진 시그널들을 더한다. 반환값은 전의 블록된 시그널들의 집합이다.

함수 : int sigsetmask (int mask)

이 함수는 how 인수가 SIG_SETMASK 인, sigprocmask와 (21. 7. 3절 [Process Signal Mask] 참조. ) 같다: 그것은 호출된 프로세스의 시그널 마스크를 set으로 정한다. 반환값은 전의 블록된 시그널들이 집합이다.

함수 : int sigpause (int mask)

이 함수는 sigsuspend(21. 8 [Waiting for a Signal] 참조. ) 과 같다: 호출된 프로세스의 시그널 마스크를 mask로 설정하고, 시그널이 도착하기를 기다린다. 반환할 때 전의 블록된 시그널의 집합은 다시 반환된다.

 

21. 10. 2 분리된 시그널 스택 사용하기

시그널 스택은 시그널 핸들러가 실행되는 동안 실행 스택으로써 사용되는 메모리의 특별한 영역이다. 오버플로우가 일어날 위험을 피하기 위해서는, 꽤 커야한다; 매크로 SIGSTKSZ는 시그널 스택을 위한 정규 크기로 정의되었다. 당신은 mallocac 을 사용해서 스택을 위한 공간을 할당할 수 있다. 그리고 나서 sigaltstack 이나 stgstack를 호출하여 시그널 스택을 사용하도록 시스템에게 알린다.

당신이 시그널 스택을 사용하기 위해서 시그널 핸들러를 달리 만들 필요는 없다. 다른 것에서 스택으로의 변경은 자동적으로 발생한다. 그렇지만, 어떤 기계 상에 존재하는 어떤 디버거는 시그널 스택을 사용하는 핸들러가 실행되는 동안 스택 트래이스(trace)를 하면 혼란스럽게 될지도 모른다.

분리된 시그널 스택을 사용하도록 시스템에게 알리기 위한 두 개의 인터페이스가 있다. sigstack은 오래된 인터페이스로써 4. 2 BSD 로부터 왔다. sigaltstack은 새로운 인터페이스로써 4. 4 BSD 로부터 왔다. sigaltstack 인터페이스는 스택의 성장 방향을 알리도록 당신의 프로그램에게 요구하지 않고, 정해진 기계와 운영체제에 의존한다는 편리점을 갖는다.

데이터 타입 : struct sigaltstack

이 구조체는 시그널 스택을 설명한다. 그것은 다음의 멤버들을 포함하고 있다:

void *ss_sp

이것은 시그널 스택의 기준(base)을 가리킨다.

size_t ss_size

`ss_sp'가 가리키는 시그널 스택의 크기(바이트)이다. 당신이 스택을 위한 공간을 얼마나 많이 할 것인지를 정한다. 다음 두 개의 매크로는 `signal. h'에 정의되어 있고, 당신은 이것은 계산된 크기로써 사용할 것이다.

SIGSTKSZ

이것은 시그널 스택을 위한 정규 크기이다. 이것은 보통 사용하기 위해서 규격화된 용량을 갖는다.

MINSIGSTKSZ

이것은 단지 운영체제가 시그널 배달을 수행하기 위해서 필요한 시그널 스택 공간의 양이다. 시그널 스택은 적어도 이것보다는 커야만 한다.
대부분의 경우, SIGSTKSZ를 사용한다. 하지만, 당신이 당신의 프로그램 시그널 핸들러가 얼마나 많은 스택 공간을 필요로 하는지 안다면, 당신은 다른 크기를 사용하기를 원할 것이다. 이 경우, 우리는 시그널 스택을 MINSIGSTKSZ로 할당하고, ss_size를 증가시킨다.

int ss_flags

이 필드는 다음의 플래그나 그들의 조합을 포함한다.

SA_DISABLE

이것은 시그널 스택을 사용하지 않을 것임을 시스템에게 알린다.

SA_ONSTACK

이것은 시스템에의해 설정되고, 현재 사용중인 시그널 스택을 가리킨다. 만일 이 비트가 설정되지 않으면, 시그널은 보통 사용자 스택 상에 배달될 것이다.

함수 : int sigaltstack (const struct sigaltstack *stack, struct sigaltstack *oldstack)

sigaltstack 함수는 시그널 핸들링 중에 사용할 대체 스택을 정한다. 시그널이 프로세스에 의해 받아들여지고 그것이 시그널 스택을 사용하도록 지정할 때, 시스템은 시그널 핸들러가 실행될 동안 사용되도록 현재 인스톨된 시그널 스택으로 변경한다.
 
만일 oldstack 이 널 포인터가 아니라면, 현재 인스톨된 시그널 스택에 대한 정보가 그곳으로 반환된다. 만일 stack이 널 포인터가 아니라면, 이것은 시그널 핸들러에 의해 사용되도록 새로운 스택으로 인스톨된다.
 
성공하면 반환값은 0이고 실패하면 -1이다. 만일 sigaltstack이 실패하면, 다음 값들 중 하나로 errno를 설정한다.

EINVAL

당신은 실제로 사용중이였던 불가능한 스택으로 시도하였다.

ENOMEM

대체 스택의 크기가 너무 작다. 그것은 적어도 MINSIGSTKSZ 보다는 커야만 한다.

다음은 오래된 sigstack 인터페이스이다. 당신은 sigaltstack 대신에 사용할 수 있다.

데이터 타입 : struct sigstack

이 구조체는 시그널 스택을 표현한다. 그것은 다음의 멤버들을 포함한다.

void *ss_sp

이것은 스택 포인터이다. 만일 당신의 기계에서 스택이 밑쪽으로 성장한다면, 이것은 당신이 할당한곳의 위를 가리킨다. 만일 그 스택이 위를 향해 성장한다면, 그것은 밑을 가리킨다.

int ss_onstack

이 필드는 만일 프로세스에서 현재 이 스택을 사용하고 있다면 참이다.

함수 : int sigstack (const struct sigstack *stack, struct sigstack *oldstack)

sigstack 함수는 시그널 핸들링 중에 사용할 대체 스택을 정한다. 시그널이 프로세스에 의해 받아들여지고 그 동작이 시그널 스택을 사용하도록 정해진다면, 시스템은 시그널 핸들러가 실행되는 동안 사용할 현재 인스톨된 시그널 스택으로 변경한다. 만일 oldstack 이 널 포인터가 아니라면, 현재 인스톨된 시그널 스택에 대한 정보가 그것이 가리키는 곳으로 반환된다. 만일 stack이 널 포인터가 아니라면, 이것은 시그널 핸들러에 의해 사용되도록 새로운 스택으로 인스톨된다. 성공하면 0을 반환하고 실패하면 -1을 반환한다.


목차 이전 : 20. 비-지역 탈출 다음 : 22. 프로세스의 시동과 종료

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

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
17. 날짜와 시간  (0) 2007.12.22
Comment 0 Trackback 0
Top

19. 지역과 세계화

목차 이전 18. 확장된 문자들 다음 : 20. 비-지역 탈출


19 지역과 세계화

다른 나라와 문명은 소통하기 위한 다양한 관습을 갖는다. 이들 관습들은 날짜와 시간을 표현하기 위한 형식처럼 아주 간단한 것에서부터 매우 복잡한 것까지 소통되는 언어의 범위를 갖는다.

소프트웨어의 세계화는 사용자가 좋아하는 관습들에 적응하도록 프로그램 하는 것을 의미한다. ANSI C에서, 세계화는 지역을 가지고 작업된다. 각 지역은 관습들의 집합에서 각 목적을 위해 선택된 한가지 관습을 지정한다. 사용자는 지역을 선택함으로써(환경변수를 거쳐서) 관습의 집합을 선택한다.

모든 프로그램들은 그들의 환경의 일부로써 선택된 지역을 상속받는다. 주어진 프로그램은 선택된 지역에 따라서 만들어졌고, 그들은 사용자에 의해 선택된 관습을 따를 것이다.


19. 1 지역이 가진 효과

각 지역은 다음의 것을 포함해서, 여러 가지 목적을 위한 관습들을 정한다.

  • 무슨 다중 바이트 문자 시퀀스가 유용한것이고, 그들이 어떻게 해석되는가 ( 18장 [Extended Characters] 참조. )
  • 지역 문자 집합 안에 있는 문자들의 분류가 알파벳처럼 되어있는지, 그리고 대, 소문자 변환 관습이 있는지.
  • 지역 언어와 문자 집합을 위한 대조 시퀀스(5. 6절 [Collating Functions] 참조.
  • 수(numbers)와 통화량(currency amounts)의 형식 (19. 6절 [Numeric Formatting] 참조.
  • 날짜와 시간의 형식 ( 17. 2. 4절 [Formatting Date and Time] 참조.
  • 에러 메시지를 포함한, 출력을 위해서 사용하는 언어는 무엇인가. ( C 라이브러리는 당신이 이것을 행하도록 아직은 도움을 주지 않는다. )
  • 예/아니오 의 질문들에 사용자의 답변을 위해 사용하는 언어는 무엇인가.
  • 훨씬 복잡한 사용자 입력을 위해 사용하는 언어는 무엇인가. ( C 라이브러리는 아직까지 이것에 대한 도움을 주지 못하고 있다. )

정해진 지역에 적응하는 어떤 것은 라이브러리 서브루틴들에 의해 자동적으로 다루어진다. 예를 들어, 선택된 지역의 대조시퀀스를 사용하여 순서대로 맞출 필요가 있는 모든 프로그램들은 비교 문자열에 strcoll 이나 strxfrm을 사용할 수 있다.

지역의 다른 것들은 라이브러리의 이해력 밖에 있다. 예를 들어, 라이브러리는 당신 프로그램의 출력 메시지를 다른 언어로 자동적으로 해석할 수 없다. 사용자가 좋아하는 언어로 출력을 지원하는 유일한 방법은 직접 프로그램 하는 것이다. ( 우리는 이것을 더 쉽게 하기 위한 도구들이 주어지기를 희망한다. )

이 장은 현재의 지역을 갱신할 수 있고, 그것의 메커니즘을 설명한다. 정해진 라이브러리 함수들상에서 현재 지역의 효과들은 각각의 함수들에 대한 설명에서 좀더 상세하게 설명되어진다.


19. 2 지역 선택하기

사용자가 한 지역을 선택하기 위한 가장 간단한 방법은 환경변수 LANG를 설정하는 것이다. 이것은 모든 목적을 위해서 사용자에게 단일한 지역을 지정한다. 예를 들어, 한 사용자가 스페인에서 가장 표준인 관습을 사용하기 위해서 `espana-castellano'라고 이름이어진 가상 지역을 선택할 수 있다.

지역들의 집합은 사용자가 사용하고 있는 운영제체에 의존하여 지원되고, 그리고 그들의 이름들을 가지고 작업한다. 우리는 `C' 또는 `POSIX'라고 불리는 표준 지역을위한 것을 제외하고는, 무슨 지역들이 존재할 것인지에 대해서 어느 약속도 할 수 없다. 사용자는 다른 목적으로 다른 지역들을 지정하기 위한 옵션들을 가진다. _실제로, 여러 개의 지역들을 혼합하여 선택한다.

예를 들어, 사용자가 여러 가지 목적을 위해서 지역 `espana-castellano'를 선택하였지만, 통화의 형식을 위해서는 지역 'usa-english'를 선택할 수 있다. 이것은 통화량이 미국 달러로 표현되지만 만일 그 사용자가 스페인에서 일하고 있고, 스페인어를 사용하는 미국인이라면 뜻이 통할 것이다.

모든 지역들처럼 `espana-castellano' 와 `usa-english' 의 두 지역은 어떤 지역들에 적용하기 위한 모든 목적들을 위한 관습을 포함한다. 그렇지만, 사용자는 그들 목적중에서 특정한 몇 가지 목적을 위해서는 다른 지역을 사용하도록 선택할 수 있다.


19. 3 지역이 미치는 활동의 범주

지역을 제공하는 목적은 범주안에 묶어지므로, 사용자나 프로그램은 독립적으로 각 범주를 위해서 그 지역을 선택할 수 있다. 다음은 범주들의 리스트이다; 각각의 이름은 사용자가 설정할 수 있는 환경변수, 그리고 setlocale에 인수로서 사용할 수 있는 매크로 이름이다.

LC_COLLATE

이 범주는 문자열의 대조를 위해서 제공된다(함수 strcoll과 strxfrm); 5. 6절 [Collation Functions] 참조.

LC_CTYPE

이 범주는 문자들의 분류와 관습, 그리고 다중 바이트와 와이드 캐릭터들에게 제공된다; 4장 [Characters Handling]18장 [Extended Characters] 참조.

LC_MONETARY

이 범주는 통화의 값들을 형식화하는데 제공된다. 19. 6절 [Numeric Formatting] 참조.

LC_NUMERIC

이 범주는 통화가 아닌 숫자값들을 형식화하는데 제공된다; 19. 6절 [Numeric Formattin] 참조.

LC_TIME

이 범주는 날짜와 시간값들을 형식화하는데 제공된다; 17. 2. 4절 [Formattin Date and Time] 참조.

LC_ALL

이것은 환경변수가 아니다; 그것은 모든 목적들을 위해서 단일한 지역을 설정하는 setlocale에서 사용할 수 있는 매크로이다.

LANG

만일 이 환경변수가 정의되면, 그 값은 위의 변수들에 의해 무시되는 것을 제외하고는 모든 목적들을 위해 사용자에게 지역을 지정한다.


19. 4 프로그램은 지역을 어떻게 설정하는가

C 프로그램은 그 프로그램이 시작할 때 지역 환경변수들을 상속받는다. 이것은 자동적으로 발생한다. 그렇지만, 그들 변수들이 라이브러리 함수들에 의해 사용된 지역은 자동적으로 제어하지 않기 때문에, ANSI C는 모든 프로그램을 디폴트로 표준 `C'지역으로 시작하게 한다. 환경변수에 의해 정해진 지역들을 사용하기 위해서는, 당신은 setlocale을 호출해야만 한다. 그것을 다음과 같이 호출하라.

setlocale (LC_ALL, "");

적당한 환경변수들에 기초한 지역을 선택하도록 한다.

당신은 또한 일반적으로 사용하거나 또는 정해진 범주를 위한, 특정한 지역을 선택하기 위해서 setlocale를 사용할 수 있다. 이 절에 있는 심볼들은 헤더파일 `locale. h'에 정의되어 있다.

함수 : char *setlocale (int category, const char *locale)

setlocale 함수는 지역에 대한 범주 category를 위해서 현재의 지역을 설정한다. 만일 범주가 LC_ALL 이면, 이것은 모든 목적을 위해서 그 지역을 지정함을 말한다. 범주의 다른 가능한 값들은 개별적 목적으로 지정한다. (19. 3절 [Locale Categories] 참조. )
 
당신은 locale인수에 널 포인터를 사용함으로서, 현재의 지역을 알아내도록 이 함수를 사용할 수 있다. 이 경우에, setlocale는 범주 category를 위해서 선택된 현재의 지역의 이름을 가진 문자열을 반환한다. setlocale에 의해 반환된 문자열은 다음에 이어지는 호출에 의해 덧씌워질 수 있으므로, 만일 당신이 전에 호출된 setlocale의 결과를 저장하기를 원한다면 당신은 그 문자열의 복사본을 만들어야 할 것이다( 5. 4절 [Copying and Concatenation] 참조. )( 표준 라이브러리는 결코 setlocale 그 자체를 호출하지 않도록 보증된다. )
 
당신은 setlocale에 의해 반환된 문자열을 갱신하지 말아야 한다. 그것은 전에 호출된 setlocale에서 인수로써 사용했던 문자열과 동일하게 될 것이다.
 
당신이 범주 LC_ALL을 위해서 현재의 지역을 읽을 때, 그 값은 모든 범주를 위해서 선택된 지역들의 전체조합으로 기호화한다. 이 경우, 그 값은 단지 단일한 지역이름이 아니다. 실제로, 그것이 무엇일지에 대해서 우리는 말할 수 없다. 그러나 만일 당신이 setlocale의 다음 호출에 LC_ALL과 함께 같은 "지역이름"을 지정하면, 그 함수는 선택된 지역과 같은 조합을 저장한다.
 
locale인수가 널 포인터가 아닐 때, setlocale에 의해 반환된 문자열은 새로인 갱신된 지역을 반영한다. 만일 당신이 loclae에 비어있는 문자열을 지정하면, 이것은 적당한 환경변수를 읽고 그 값을 범주를 위한 지역을 선택하는데 사용한다는 의미이다. 만일 당신이 유용하지 않은 지역이름을 지정하면, setlocale는 널 포인터를 반환하고 현재의 지역은 변화되지 않는다.
 
다음은 당신이 새로운 지역으로 일시적인 변환을 위해서 setlocale를 어떻게 사용할 것인지에 대한 예제이다.
#include <stddef. h>
#include <locale. h>
#include <stdlib. h>
#include <string. h>
 
void
with_other_locale(char *new_locale, void (*subroutine)(int), int argument)
{
char *old_locale, *saved_locale;
 
/* 현재 지역의 이름을 얻어라. */
old_locale = setlocale (LC_ALL, NULL);
 
/* setlocale에 의해 변경되지 않은 이름을 복사하라 */
saved_locale = strdup (old_locale);
if (old_locale == NULL)
fatal ("Out of memory");
 
/* 이제 그 지역을 변경하고, 그것으로 어떤 자료를 하라 */
setlocale (LC_ALL, new_locale);
(*subroutine) (argument);
 
/* 원래의 지역을 다시 저장하라 */
setlocale (LC_ALL, saved_locale);
free (saved_locale);
}
 
이식성 노트 : 어떤 ANSI C 시스템들은 부가적인 지역 범주들을 정의할 수 있을 것이다. 이식성을 위해서, `LC_'로 시작하는 어느 심볼은 `locale. h'에 정의되었다고 생각하자.


19. 5 표준 지역들

모든 운영체제상에서 당신이 발견할 수 있는 지역의 이름은 오직 다음의 세 가지뿐이다:

"C"

이것은 표준 C 지역이다. 그것이 제공하고 있는 속성과 행동은 ANSI C 표준에 의해 지정되었다. 당신의 프로그램이 시작될 때, 그것은 처음에 디폴트로 이 지역을 사용한다.

"POSIX"

이것은 표준 POSIX 지역이다. 일반적으로 이것은 표준 C 지역을위한 다른으로 여겨지고 있다.

" "

이 빈(empty) 이름은 환경변수들에 기초한 지역을 선택함을 말한다. 19. 3절 [Locale Categories] 참조.

이름을 가진 지역들을 정의하고 구동하는 것은 보통 당신이 속한 시스템의 시스템관리자의 책임이다(또는 GNU C 라이브러리를 구동했던 사람. ) 어떤 시스템들은 사용자가 지역을 만들도록 허용할 수도 있지만, 우리는 이것에 대해서 논의하지 않겠다.

만일 당신의 프로그램이 `C'지역과는 다른 어떤 것을 사용할 필요가 있다면, 명백하게 어떤 비-표준 지역을 이름으로 지정하기보다는, 환경변수를 통해서 사용자가 정한 지역을 따르도록 한다면 좀 더 이식성이 있는 프로그램이 될 것이다. 다른 기계에서는 자신이 구동한 지역과는 다른 지역들의 집합을 가질지도 모른다는 것을 기억하라.


19. 6 숫자의 형식화

당신이 현재 지역의 관습을 사용해서 숫자나, 통화량을 형식화하길 원한다면, 그것을 어떻게 형식화할 것인지에 대한 정보를 얻도록 localeconv 함수를 사용할 수 있다. localeconv 함수는 헤더파일 `locale. h'에 선언되어 있다.

함수 : struct lconv * localeconv (void)

localeconv 함수는 현재의 지역에서 숫자와 통화 값이 어떻게 형식화되는지에 대한 정보가 포함된 요소를 가진 구조체를 가리키는 포인터를 반환한다. 당신은 그 구조체나 내용을 변경할 수 없다. 구조체는 localecnov의 다음 호출에 의해 덧씌워지거나, 호출된 setlocale에 의해 변경되지만, 라이브러리에 있는 어느 다른 함수들은 이 값을 변경할 수 없다.

데이터 타입 : struct lconv

이것은 localeconv에 의해 반환된 값의 데이터타입이다.
만일 구조체 struct lconv의 멤버가 char 형을 가진다면, 그리고 그 값이 CHAR_MAX 라면, 그것은 현재의 지역을위한 파라미터에 아무런 값이 없음을 의미한다.

 

19. 6. 1 일반적인 숫자 형식화 파라미터

다음은 struct lconv의 표준 멤버들이다; 다른 것이 있을 수도 있다.

    char *decimal_point

    char *mon_decimal_point

    통화량이 아닌것과 통화량을 형식화하는데 사용되는 소수점 분리자(separators)이다. `C' 지역에서는, 소수점의 값은 ". "이고, mon_decimal_point의 값은 ""이다.

    char *thousands_sep

    char *mon_thousands_sep

    통화량이 아닌것과 통화량을 형식화할 때, 소수점 왼쪽의 십진수의 한계그룹에 사용되는 분리자(separators)이다. `C' 지역에서, 이 둘 멤버들은 ""(빈 문자열)의 값을 갖는다.

    char *grouping

    char *mon_grouping

    grouping은 통화량이 아닌것에 적용되고 mon_grouping은 통화량에 적용해서 소수점왼쪽에 있는 숫자들을 어떻게 묶을 것인지를 정하는 문자열이다. 십진수 그룹들 분리하는데는 thousnads_sep나 mon_thousands_sep중에 하나를 사용하라.
     
    각 문자열은 세미콜론에 의해 분리된 십진수를 구성한다. 연속적인 숫자들(왼쪽부터 오른쪽으로)은 연속적인 그룹들( 소수점에서 시작하여, 오른쪽에서부터 왼쪽으로)의 크기를 준다. 문자열에서 마지막 숫자는 모든 남겨진 그룹들을 위하여 되풀이해서 사용되어진다. 만일 마지막 정수가 -1이라면, 그것은 더이상 묶을 것이 없거나_또는 남겨진 숫자들이 분리자가 없는 하나의 큰 그룹을 구성하고 있는, 다른 방법으로 놓여져 있음을 의미한다.
     
    예를 들어, 만일 묶음(grouping)이 "4; 3; 2"라면, 숫자 123456787654321을 위한 정확한 그룹은 `12', `34', `56', `78', `765', `4321' 이다. 이것은 2 숫자그룹과 3 숫자 그룹에 의해 선행된, 마지막에 있는 4 숫자그룹을 사용한다. 그 숫자는 분리자 `, '를 사용하여, `12, 34, 56, 78, 765, 4321'. 와 같이 출력되어진다.
     
    "3"의 값은 보통 미국에서 사용되어, 세 개 숫자들의 반복된 그룹들을 지적한다. 표준 `C' 지역에서, grouping 과 mon_grouping이 모두 ""의 값을 가지면, 이 값은 전혀 묶음을 하지 않음을 의미한다.

    chart_frac_digits

    char frac_digits in

    이것은 국제와 지역 형식 각각에서 통화 값을 나타내는데 있어서 얼마나 많은 소수부(소수점의 오른쪽)를 사용할 것인가를 나타낸다. (아주 자주, 두 개의 멤버들은 같은 값을 갖는다. )

표준 `C'지역에서, 이들 멤버 둘은 모두 "미지정"이라는 의미를 가진 CHAR_MAX 값을 갖는다. ANSI 표준은 당신이 이것을 발견했을 때 그 값으로 무엇을 할 것인지 말하지 않는다. ; 우리는 아무런 소수부가 출력되지 않는 것을 추천한다. ( 이 지역은 또한 mon_decimal_point를 위해 빈 문자열을 지정하는데, 그것은 어느 소수부 숫자들의 출력도 거부됨을 의미한다. )

 

19. 6. 2 통화 기호 ( Currency Symbol ) 출력하기

구조체 struct lconv의 멤버들은 통화 값을 확인하는 심볼들을 어떻게 출력할 것인지를 정한다_미국달러의 표시는 `$'으로 세계적으로 인정한다.

각 나라는 두 개의 표준 통화기호들을 가지고 있다. 지역 통화기호는 그 나라안에서 통상적으로 사용되는것이고, 세계 통화기호는, 명백하게 그 나라를 지정할 필요가 있을 때 그 나라의 통화기호로 나타내기 위해서 국제적으로 사용된다.

예를 들어, 많은 나라들은 그들의 통화단위로 달러를 사용하고, 국제적으로 다른 나라들과 거래를 할 때 미국 달러대신에 캐나달 달러로 거래할것인지 또는 오스트레일리아 달러로 거래할것인지를 정하는 것은 중요하다. 그러나 그 구문이 캐나다가 되도록 정해져있을 때, 달러의 양들은 캐나다 달러로 된다고 가정되므로 이것을 명백하게 만들 필요가 없다.

char *currency_symbol

선택된 지역을위한 통화기호. 표준 `C' 지역에서, 이 멤버는 "미지정"을 의미하는, ""(빈 문자열)의 값을 갖는다. ANSI 표준은 당신이 이것을 발견했을 때, 그 값으로 무엇을 하는지를 말하지 않는다. 우리는 당신이 적당한 멤버에서 발견한 어느 문자열을 출력하는 것처럼, 빈 문자열을 간단히 출력하도록 권장한다.

char *int_curr_symbol

선택된 지역을위한 국제 통화기호. int_curr_symbol의 값은 국제 표준 ISO 4217 코드들에 의해 결정된 세 개-문자의 약어로 구성된다. 국제표준 ISO 4217코드는 한-문자 분리자의 다음에 나타나는(거의 공백을 사용. ), 통화와 자금의 표현을 위한 것이다. 표준 `C'지역에서, 이 멤버는 "미지정"을 의미하는 ""(빈 문자열)의 값을 갖는다. 우리는 적당한 멤버에서 발견된 어느 다른 문자열을 출력하는것처럼 빈 문자열을 간단히 출력하기를 권장한다.
char p_cs_precedes
char n_cs_precedes

이들 멤버들은 만일 currency_symbol 문자열이 통화량의 값보다 선행하면 1이고, 그 문자열이 값보다 뒤에 나타나면 0이다. p_cs_precedes 멤버는 양의 값(또는 0)에 적용하고, n_cs_precedes 멤버는 음의 값에 적용한다. 표준 `C' 지역에서, 이 멤버 둘 모두는 "미지정"을 의미하는 CHAR_MAX의 값을 갖는다. ANSI 표준은 당신이 이 값을 발견할 때 무엇을 하는지를 말하지 않지만, 우리는 그 값 앞에 통화기호를 출력하기를 권장한다. 대부분의 나라에서 그렇게 사용한다. 즉 그들 멤버 안에 있는 모든 0이 아닌 값들을 유사하게 취급하라.

POSIX 표준은 그들 두 개의 멤버들을 currency_symbol은 물론 int_curr_symbol에도 적용한다. ANSI C 표준은 그들은 오직 currency_symbol에 적용됨을 내포하고 있는 것처럼 보여서_int_curr_symbol은 항상 나타내려는 양(amount)에 선행할 것이다. 우리는 그들이 국제통화기호를 출력하기 위해서 보통의 관습들과 매치될것이라고 짐작할 수 있다. 우리의 나타내려는 양(amount)에 항상 선행할 것이라고 추측한다. 만일 우리가 이해 가능한 답을 발견한다면, 우리는 그것을 그렇게 할 것이다.

  • char p_sep_by_space
  • char n_sep_by_space

그들 멤버들은 currency_symbol 문자열과 양(amount) 사이에 공백이 있으면 1이고 없으면 0이다. p_sep_by_space 멤버는 양의값(또는 0)에 적용되고, n_sep_by_space 멤버는 음의 값에 적용된다. 표준 `C' 지역에서, 이들 멤버 둘 모두는 "미지정"을 의미하는 CHAR_MAX의 값을 갖는다. ANSI 표준은 당신이 이 값들을 발견할 때 당신이 무엇을 할 것인지를 알리지 못한다; 우리는 공백을 출력하는 것 그것을 취급하기를 제안한다. 즉, 그들 멤버 안에 있는 모든 0이 아닌 값들은 유사하게 최급하라. 그들 멤버들은 오직 currency_symbol에 적용된다. 당신이 int_curr_symbol을 사용할 때, 당신은 결코 부가적인 공백을 프린트하지 않는다, 왜냐하면, int_curr_symbol 그 자체는 적당한 분리자를 포함하고 있기 때문이다.

POSIX 표준은 그들 두 개의 멤버들을 currency_symbol 처럼 int_curr_symbol에도 적용한다. 그러나 ANSI C 표준에 있는 예제들은 적당한 분리자를 포함한 int_curr_symbol로 currency_sumbol만이 오직 적용됨을 보여주고 있다_그래서 당신은 결코 부가적인 공백을 프린트하지 않을 것이다. 우리가 현재 알고있는 것에 기초하여, 우리는 당신이 국제 통화기호를 프린트하고 아무런 공백을 프린트하지 않을 때, 그들 멤버들을 무시하기를 권장한다.

 

19. 6. 3 금전( Money ) 부호를 프린트하기

구조체 struct lconv의 멤버들은 통화 값의 부호를 어떻게 프린트할 것인지를 지정한다.

char *positive_sign

char *negative_sign

그들은 양의값(또는 0) 와 통화량을 알리기위해 사용되는 문자열이다. 표준 `C' 지역에서, 그들 멤버 둘 모두는 "미지정"을 의미하는 ""(빈 문자열)의 값을 갖는다.
 
ANSI 표준은 당신이 이 값을 발견할 때 무엇을 하도록 말하지 않는다; 우리는 당신이 그것을 발견했을 때, 심지어 만일 그것이 비어있을 때라고, positive_sign을 프린트하기를 권장한다. 음의 값을 위해서는, nugative_sign과 positive_sign이 모두 비어있지 않다면 당신이 발견할 것으로negative_sign을 프린트하라, 이 경우에는 `-'이다. ( 전혀 어떤 부호라도 지시하는 것을 실패하면 나중에 이해할 수 없는 값이 되고 만다. )

char p_sign_posn

char n_sign_posn

0 통화기호와 통화량이 괄호가 쳐져있다.
1 통화기호와 통화량 앞에 부호 문자열을 출력하라.
2 통화기호와 통화량 뒤에 부호문자열을 출력하라.
3 통화기호 앞에 적당한 부호 문자열을 출력하라.
4 통화기호 후에 적당한 부호 문자열을 출력하라.

CHAR_MAX

"미지정" 두 개 멤버 모두는 표준 `C' 지역에서 이 값을 갖는다.

ANSI 표준은 CHAR_MAX 값이 있을 때 당신이 무엇을 해야하는지 말해주지 않는다. 우리는 통화기호 뒤에 부호를 출력하도록 권장한다.

국제적 통화 형식이거나 아닌것에 이들 멤버들의 적용이 당신에게 허용 될 것인지가 명백하지 않다. POSIX는 당신이 그렇게 하도록 하지만, ANSI C 표준에 있는 예제들은 당신이 그렇게 하지 않기를 제안한다. 우리는 통화량의 형식화를 위한 관습을 잘 알고 있는 누군가가 우리가 무엇을 권장할지를 우리에게 말해주기를 희망한다.


목차 이전 18. 확장된 문자들 다음 : 20. 비-지역 탈출

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

22. 프로세스의 시동과 종료(Process StartUp)  (0) 2007.12.22
21. 신호처리(Signal)  (0) 2007.12.22
19. 지역과 세계화  (0) 2007.12.22
18. 확장된 문자  (0) 2007.12.22
17. 날짜와 시간  (0) 2007.12.22
16. 형식일치( Pattern Matching )  (0) 2007.12.22
Comment 0 Trackback 0
Top

18. 확장된 문자

목차 이전 : 17. 날짜와 시간 다음 : 19. 지역과 세계화


18 확장된 문자들

다수의 언어들이 char의 형태로 문자들을 표현할 수 없다. 일본어와 중국어가 이와 가장 유사한 예이다. GNU C 라이브러리는 확장된 문자 집합들을 취급하기 위해서 두 개의 메커니즘을 지원한다: 다중 바이트 문자들과 와이드 캐릭터(wide characters) 이 장은 이들 메커니즘과 그들을 변환하는 함수들을 어떻게 사용할 것인지를 설명한다. 이 장에 있는 함수들의 동작은 문자 분류 LC_CTYPE 범주로 나타낸 현재의 지역에 의해 영향을 받는다; 19. 3절 [Locale Categories] 참조. 지역을 선택하는 것은 다중 바이트 코드가 사용되는지, 와이드 캐릭터 코드가 사용되는지를 선택하는 것이다.


18. 1 확장된 문자들에 대한 소개

당신은 두 가지 방법중에 하나로 확장 문자들을 나타낼 수 있다.

다중 바이트 문자들은 char 오브젝트의 배열인 원래의 문자열에 포함 될 수 있다. 이것의 이득은 많은 프로그램들과 운영제체가 아무런 변환 없이, 원래의 ASCII 문자들 사이에 흩어져 있는 다중 바이트 문자들을 다룰 수 있다는 것이다.

와이트 캐릭터는 더 많은 비트들을 점유한다는 것을 제외하고는 원래의 문자들과 같다. 와이트 캐릭터 데이터 타입, wchar_t는 ASCII 코드들 은 물론 확장된 문자 코드들을 다루기 위해서 충분히 큰 범위를 갖는다. 와이드 캐릭터의 이득은 각 문자들이 원래의 ASCII 문자들처럼 단일한 데이터 오브젝트라는 것이다. 하지만 몇 개의 불편이 있다.

어떤 현존하는 프로그램이 와이트 캐릭터를 사용하도록 하려면 갱신되고 재컴파일 되어야만 한다.

와이드 캐릭터의 파일들은 보통의 문자들을 제외하고는 프로그램에 의해 읽혀질 수 없다.

일반적으로, 당신은 파일에 텍스트를 읽거나 쓰기처럼, 외부 프로그램 인터페이스의 부분으로 다중 바이트 문자 표현을 사용한다. 그렇지만, 일정한 표현은 편집 명령들을 가장 쉽게 구현하도록 하기 때문에, wchar_t 오브젝트의 배열로 확장된 문자들을 포함하는 문자열을 내부적인 처리를 수행하는 것이 보통은 더 쉽다. 만일 당신이 파일에서는 다중 바이트 문자들을 사용하고 내부 동작에는 와이트 캐릭터를 사용한다면, 당신은 데이터를 읽거나 쓸 때, 그들 사이에 변환을 할 필요가 있다. 만일 당신의 시스템이 확장된 문자들을 지원한다면, 다중 바이트 문자들과 와이드 캐릭터 문자들을 둘다 지원한다. 라이브러리에 포함된 함수를 통해 당신은 그 두 표현 사이의 변환을 수행할 수 있다. 이들 함수들은 이 장에 설명되어 있다.


18. 2 지역과 확장된 문자

컴퓨터 시스템은 한 개의 다중 바이트 문자 코드보다 많이, 그리고 한 개의 와이트 캐릭터 코드보다는 많이 지원할 수 있다. 사용자는 문자 분류에 있는 현재의 지역코드를 선택함으로써 제어한다( 19장 [Locals] 참조. ) 각 지역은 특정한 다중 바이트 문자 코드와 특정한 와이드 캐럭터 코드를 정한다. 지역의 선택은 라이브러리에 있는 변환 함수들의 동작에 영향을 끼친다. 어떤 지역들에서는 와이드캐릭터나 혹은 다중 바이트 문자들중 하나만 지원한다. 이들의 지역에서, 라이브러리의 변환 함수들은 비록 하찮은 일들이지만 여전히 작업한다.

만일 당신이 문자분류를 위해서 새로운 지역을 선택한다면, 그들 함수들에 의하여 유지되고 있던 내부 shift 상황은 혼란하게 될 수도 있다, 그러므로 당신이 문자열을 처리하는 도중에 지역을 변경하는 것은 좋은 생각이 아니다.


18. 3 다중 바이트 문자들

보통의 ASCII 코드에서 연속된 문자들은 바이트가 연속하고 있음을 의미하고 각 문자는 한 바이트이다. 이것은 매우 간단하지만, 오직 256개의 독립문자들만을 허용한다.

다중 바이트 문자 코드에서도, 연속된 문자들은 바이트가 연속하고 있음을 의미하지만, 각 문자는 한 개 또는 더 많은 연속적인 바이트들을 점유하고 있을 것이다.

다중 바이트 코드를 설계하는 많은 다른 방법들이 있다; 다른 시스템들은 다른 코드들을 사용한다. 특정한 코드를 설계하기 위해서는 특정한 문자를 대표하는 한 문자를 몇바이트(기본 바이트 시퀀스)로 나타낼 것인지를 정해야 한다. 컴퓨터에서 한 코드는 실제로 기본 시퀀스들의 제한된 개수를 가져야만 하고, 그들 중 어떤 것도 이것보다 길어서는 안된다.

이들 시퀀스들이 모두 같은 길이를 가질 필요는 없다. 실제로, 그들 중 대부분은 단지 한바이트 길이를 가진다. 0에서 0177의 범위에 있는 기본 ASCII 문자들이 중요하기 때문에, 그래서 그들을 모두 다중 바이트 코드로 나타낸다. 0에서 0177의 범위에 있는 한 바이트는 그 자체로 한 문자를 표현하고 있다. 한 바이트보다 긴 문자들은 0200에서 0377 범위에 있는 한바이트로 항상 시작되어야만 한다. 바이트 값 0은 문자열을 끝내기 위해서 사용될 수 있고, 종종 ASCII 문자들의 문자열에 사용된다.

단일한 문자들을 표현하는 기본 바이트 시퀀스를 정하는 것은 자동적으 로긴 바이트 시퀀스에 의미를 주게된다. 예를 들어, 두 개의 바이트 시퀀스 0205 049가 그리스 문자 알파를 의미한다면, 0205 049 065는 알파 뒤에 'A'(ASCII 코드 065)가 따라옴을 나타내고, 0205 049 0205 049는 일렬로 두 개의 알파가 있음을 나타낸다.

만일 어느 바이트 시퀀스가 여러 가지의 의미로 해석될 수 있다면 그 다중 바이트 코드는 좋은 것이 아니다. 시스템에서 코드는 실제로 모두 단일한 의미로 사용된다. 대부분의 코드에서 어떤 바이트들의 시퀀스는 문자로써 아무런 의미를 가지지 않는다. 그들을 유용하지 않다라고 한다.

가장 간단하고 가능한 다중 바이트 코드에 대한 한가지 :
단일한 바이트들로 구성된 기본 시퀀스
 
특정한 코드는 전혀 다중 바이트 코드를 사용하지 않는다. 그것은 유용하지 않은 시퀀스가 없다. 그러나 오직 256개의 문자들만을 취급할 수 있다. 이곳에 9376개의 다른 문자들을 다룰 수 있는 다른 가능한 코드가 있다.
 
기본 시퀀스의 구성은. 0에서 0237은 단일한 바이트값.
0240에서 0377 범위에 있는 값들을 가진 바이트 두 개로 두 개-바이트 시퀀스
 
이 코드 또는 이와 유사한 것들은 일본어 문자들을 표현하기 위해서 어떤 시스템에서 사용된다. 유용하지 않은 시퀀스는 0240부터 0377의 범위에 있는 연속된 바이트들의 개수가 홀수라면 유용하지 않은 것이다.
여기에 더 많은 확장된 문자들을 다룰 수 있는 다중 바이트 코드가 있다. 이것은 실제로 거의 3000만 까지 된다.
 
기본 시퀀스 구성은. 0에서 0177의 범위로는 단일한 바이트.
네 개의 바이트로 구성된 시퀀스에서 첫 번째 바이트는 0200에서 0237의 범위를 갖고, 나머지는 0240에서 0377의 범위를 갖는다.
 
이 코드에서, 어느 시퀀스가 0240에서 0377의 범위의 한바이트로 시작하지 않으면 유용하지 않다. 그리고 마지막 바이트를 제거함으로써 어떤 이득을 갖거나, 한 개의 유용한 문자를 대표하는 바이트들을 가지고 결코 다른 유용한 문자을 만들어낼 수 없는 문자코드가 있다. (이런 특성은 당신이 특정한 문자들이 있는 문자열을 탐색하고자 할 때 편리하다. )
 
기본 시퀀스 구성은. 0에서 0177의 범위로는 단일한 바이트.
두 개-바이트 시퀀스에서 첫 번째 바이트는 0200에서 0207의 범위를 갖고, 두 번째는 0240에서 0377의 범위를 갖는다.
세 개-바이트 시퀀스에서 첫 번째 바이트는 0210에서 0217의 범위를 갖고, 나머지 바이트들은 0240에서 0377의 범위를 갖는다.
네 개-바이트 시퀀스에서 첫 번째 바이트는 0220에서 0227의 범위를 갖고, 나머지 바이트들은 0240에서 0377의 범위를 갖는다.
 
이 코드로 구성된 유용한 시퀀스들은 길다; 유용한 시퀀스의 예로 0240 과 0220 0300 065가 있다. 가능한 다중 바이트 코드들의 개수는 셀수가 없다. 그러나 주어진 컴퓨터 시스템은 고작 몇 개의 다른 코드들을 지원할 것이다. ( 그들 코드들중 하나는 수천 개의 다른 문자들을 허용할 것이다. ) 다른 컴퓨터 시스템은 한 개의 완전한 다른 코드를 지원할 것이다. 이 장에서 설명된 라이브러리도구들은 당신의 프로그램에서 그들을 알 필요가 없이 특정 컴퓨터 시스템의 다중 바이트 코드에 대한 세심한 지식들로 꾸려져있기 때문에 유용하다.

당신이 현재 선택된 다중 바이트 코드에서 한문자를 표현하는 바이트들의 최대 가능 개수를 찾기 위해서는 표준 매크로 MB_CUR_MAX를 사용하고, 당신의 컴퓨터에서 지원되는 어느 다중 바이트 코드를 위한 최대 개수를 위해서는 MB_LEN_MAX를 사용하라.

매크로 : int MB__LEN__MAX

이것은 어느 지원된 지역을위한 한 다중 바이트 문자의최대 길이이다. `limits. h'에 정의되어 있다.

매크로 : int MB__CUR__MAX

이 매크로는 현재의 지역에서 한 다중 바이트 문자를 표현하는 최대 바이트의 개수를 양의 정수표현으로(가능하게비-상수)확장한다. 그 값은 결코 MB_LEN_MAX보다 길 수가 없다.
MB_CUR_MAX는 `stdlib. h'에 정의되어 있다.

보통, 특정 문자 코드 안에 있는 각각의 기본 시퀀스는 한 문자를 나타낸다. 같은 다중 바이트 문자 코드들은 한 shift 상황을 갖는다; 어떤 코드들에서는, 쉬프트 시퀀스라고 불리는 다른 쉬프트 상황으로 변경하고, 어떤 또는, 모든 기본 시퀀스들의 의미는 현재의 쉬프트 상황에 따른다. 실제로, 기본 시퀀스의 집합은 심지어 현재의 쉬프트 상황에 의존하여 달라질 것이다. 이 코드의 종류를 다루는 것에 대한 정보는 18. 9절 [Shift State] 를 참조하라.

포함된 다중 바이트 문자열을 그들에 대해서 알지 못하는 함수에게 주려 시도할 때 무슨 일이 일어날것인가? 보통, 함수는 바이트들의 시퀀스로 문자열을 취급하고, 특별한 어떤 바이트값으로 해석한다; 모든 다른 바이트값들은 "보통"이다. 다중 바이트의 길이를 가진 문자가 어느 특별한 바이트 값들을 포함하지 않으면, 그 함수는 몇 개의 보통 문자처럼 그들을 취급할 것이다.

예를 들어, 만일 당신이 파일의 이름으로 다중 바이트 문자들을 사용한다면 무엇이 발생할지를 생각해보자. open과 unlink와 같은 함수들은 특별한 값으로 `/'와 함께, 연속된 바이트들의 시퀀스로 그 이름을 취급하여 함수이름을 가지고 동작한다. 어느 다른 바이트 값들이 시퀀스로 복사되거나 비교되면, 모든 바이트값들은 유사하게 취급된다. 그러면, 당신은 연속된 바이트들로 구성된 파일이름으로 생각하거나, 또는 다중 바이트 문자들이 포함된 문자열로 생각할 것이다; 그 같은 동작은 `/'를 포함하지 않은 다중 바이트 문자에도 같은 방법중 하나로 이해될 것이다.


18. 4 와이드 캐릭터에 대한 안내

와이드 캐릭터는 다중 바이트 문자보다는 많이 간단하다. 그들은 8비트 이상을 가지고 문자들을 표현하기 때문에, 256개보다는 많은 문자들을 표현할 수 있다. 와이드 캐릭터 데이터 타입, wchar_t는 규격화된 ASCII 코드는 물론 확장된 문자 코드들을 저장하기에 충분한 범위를 갖고 있다. 와이드 캐럭터의 사용으로 갖는 이득은 각각의 문자가 보통의 ASCII 문자들처럼, 단일한 데이터 오브젝트라는 것이다. 그렇지만 와이드 캐럭터들은 몇 가지 불편함을 내포하고 있다.

프로그램이 와이드 캐릭터를 사용하려면 수정되고 재컴파일 되어야만 한다.

와이드 캐릭터로 이루어진 파일들은 보통의 문자들을 제외하고는 프로그램에 의해 읽혀질 수 없다.

와이드 캐럭터 값, 0에서 0177 사이는 항상 ASCII 문자 코드와 동일하다. 와이드 캐럭터값 0은 , 0의 값을 가진 단일한 바이트가 보통의 문자들로 이루어진 문자열을 종료하는데 사용되는 것처럼, 와이드 캐릭터들로 이루어진 문자열을 종료하는데 사용된다.

데이터 타입 : wchar__t

이것은 지원된 지역들에서 사용되는 확장된 문자 값들을 모두 표현하기에 충분히 큰 범위를 가진 정수형으로 "와이드 캐럭터"의 데이터형이다. 지역에 대한 정보는 19장 [Locales] 를 참조하라. 이 형은 헤더파일 'stddef. h'에 정의되어 있다.

만일 당신의 시스템이 확장된 문자들을 지원한다면, 각각의 확장된 문자들은 한 개의 와이드 캐릭터 코드와 그에 해당하는 다중 바이트 기본 시퀀스를 둘다 갖는다.


18. 5 확장된 문자열의 변환

mbstowcs함수는 다중 바이트 문자들로 이루어진 문자열을 와이드 캐릭터 배열로 변환한다. wcstombs 함수는 그 역변환을 한다. 이들 함수들은 헤더파일 'stdlib. h'에 선언되어 있다.

대부분의 프로그램에서, 이들 함수들은 와이드 문자열과 다중 바이트 문자열 사이의 변환을 위해서 당신이 필요한 것들이다. 그러나 그들은 제한들을 갖는다. 만일 당신의 데이터가 널문자로 끝나지 않거나, 동시에 모두 코어 안에 있지 않다면, 당신은 아마도 한 번에 한 문자를 변환하는 저-수준 변환함수를 사용해야 할 것이다. 18. 7절 [Converting One Char] 참조.

함수 : size_t mbstowcs (wchar_t *wstring, const char *string, size_t size)

mbstowcs("다중 바이트 문자열을 와이드 캐릭터 문자열로")함수는 널 종료 문자로 끝나는 다중 바이트 문자들로 이루어진 문자열을 와이드 캐릭터 코드들의 배열로 변환한다. 그 배열은 wstring에서 시작하는 배열에 와이드 문자들을 저장하는데, 그 배열의 크기가 size보다는 클 수 없다. size의 크기에는 널 종료문자가 포함되지 않았다, 그래서 만일 size가 이 함수의 실행 결과인 문자열에서 와이드 캐럭터의 실제 개수보다 작았다면, 널 종료문자는 저장되지 않게된다.
 
문자열을 가지고 문자들을 변환하는 것은 초기 쉬프트 상황으로 시작한다. 만일 유용하지 않은 다중 바이트 문자 시퀀스가 발견되면, 이 함수는 -1의 값을 반환하고, 그렇지 않다면, 배열 wstring에 저장 된 와이드 캐럭터의 개수를 반환한다. 이 개수는 널 종료문자가 포함되지 않은 것으로 만일 개수가 size보다 작으면 널 종료문자가 저장된다.
 
다음은 결과를 저장하기에 충분한 공간을 할당해서, 다중 바이트 문자열을 어떻게 변환하는지를 보여주는 예제이다.
 
wchar_t *
mbstowcs_alloc (const char *string)
{
size_t size = strlen (string) + 1;
wchar_t *buf = xmalloc (size * sizeof (wchar_t));
size = mbstowcs (buf, string, size);
if (size == (size_t) -1)
return NULL;
buf = xrealloc (buf, (size + 1) * sizeof (wchar_t));
return buf;
}

함수 : size_t wcstombs (char *string, const wchar_t wstring, size_t size)

wcstombs("와이드 캐럭터 문자열을 다중 바이트 문자열로")함수는 널 종료문자로 끝나는 와이드 캐럭터 배열 wstring을 다중 바이트 문자열로 변환하는 함수로, string에서 시작하여 size 바이트보다 크지않게 저장하는데, 그 size의 크기가 변환된 문자열을 저장하기에 충분하다면 널 종료문자도 저장된다. 문자들의 변환은 초기 쉬프트 상황으로 시작한다.
 
널 종료문자는 size의 크기에 포함되지 않기 때문에, 만일 size의 크기가 wstring에서 필요로 하는 바이트의 개수와 같거나 더 적다면, 널 종료문자는 저장되지 않는다. 만일 유용한 다중 바이트 문자에 해당되지 않은 코드가 발견되면, 이 함수는 -1의 값을 반환한다. 그렇지 않다면, 반환값은 배열 string에 저장된 바이트들의 개수이다. 이 개수는 널 종료문자가 포함되지 않은 것으로, 그 개수가 size 보다 적다면 널 종료문자가 저장된 것이다.


18. 6 다중 바이트 문자 길이

이 절은 다중 바이트 문자들로 이루어진 문자열을 한 번에 한문자씩 어떻게 조사하는지를 설명하고 있다. 이런 일을 하는데 따르는 어려움은 각각의 한 문자가 몇 개의 바이트로 이루어졌는지를 알아야하는 것이다. 당신은 이것을 판단하는데 mblen 함수를 사용할 수 있다.

함수 : int mblen (const char *string, size_t size)

널 종료문자가 없는 문자열을 가지고 mblen함수를 사용하면, string에서 시작하여 다중 바이트 문자를 구성하는 바이트의 개수를 반환하는데, size바이트 만큼만 시험한다. ( 여기서 size는 당신이 가지고 있는 데이터의 바이트의 개수로 공급한다. ) mblen의 반환값은 세 가지 가능성으로 구분된다: string의 첫 번째 size 바이트들이 유용한 문자로 시작한는지, 유용하지 않은 바이트 시퀀스로 시작하거나 단지 한 문자의 일부인지, 비어있는 문자열인지. 유용한 다중 바이트 문자에서는, mblen은 그 문자(적어도 한 개 이상이고, 결코 size보다 커서는 안된다)의 바이트들의 개수를 반환한다. 유용하지 않은 바이트 시퀀스에서는, mblen은 -1을 반환한다. 비어있는 문자열에서는, 0을 반환한다.
 
만일 다중 바이트 문자 코드가 쉬프트 문자들을 사용한다면, mblen은 그것을 조사해서 쉬프트 상황을 갱신하고 유지한다. 만일 당신이 string으로 널 포인터를 사용하여 mblen을 호출한다면, 표준 초기값으로 쉬프트 상황을 초기화한다. 그것은 만일 실제로 사용된 다중 바이트 문자 코드가 쉬프트 상황을 갖고 있다면 0이 아닌 값을 반환한다. 18. 9절 [Shift State] 를 참조하라. mblen 함수는 `stdlib. h'에 선언되어 있다.


18. 7 일 대 일로 확장된 문자들의 변환

당신은 mbtowc 함수를 사용해서 다중 바이트 문자를 한번에 한자씩 와이드 캐릭터로 변환할 수 있다. wctomb 함수는 그 반대 작용을 한다. 이들 함수들은 `stdlib. h'에 선언되어 있다.

함수 : int mbtowc (wchar_t *result, const char *string, size_t size)

mbtowc( 다중 바이트 문자를 와이드 캐릭터로 ) 함수는 널 종료 문자가 없는 문자열을 가지고 호출했을 때, string의 처음에 있는 첫 번째 다중 바이트 문자를 그에 해당하는 와이드 캐릭터 코드로 변환한다. 그것은 결과를 *result에 저장한다.
 
mbtowc는 size 바이트 만큼만 시험한다. ( size는 당신이 가지고 있는 데이터의 바이트의 개수로 공급한다. ) 널 종료문자가 없는 string으로 호출된 mbtowc는 세 가지 가능성으로 구분된다: string에 있는 첫 번째 size 바이트들이 유용한 다중 바이트 문자로 시작하는지, 그들이 유용하지 않는 바이트 시퀀스로 시작하거나 단지 한 문자의 일부분인지, 또는 string이 비어있는( 한 개의 널 문자) 문자열을 가리키는지. 유용한 다중 바이트 문자라면, mbtowc 는 그것은 와이드 캐릭터로 변환하여 *result에 저장하고, 그 문자의 바이트 개수를 반환한다 ( 적어도 한 개 이상이고, 결코 size보다 커서는 안된다. ). 유용하지 않은 바이트 시퀀스라면, mbtowc는 -1을 반환한다. 비어있는 문자열이라면, 0을 반환하고, 또한 *result에도 0을 저장한다.
 
만일 다중 바이트 문자 코드가 쉬프트 상황을 사용한다면, mbtowc는 그것을 검사하여 쉬프트 상황을 유지하고 갱신한다. 만일 당신이 string에 널 포인터를 사용하여 mbtowc를 호출하면, 그것은 표준 초기값으로 쉬프트 상황을 초기화한다. 그것은 또한 만일 사용하고 있는 다중 바이트 코드가 쉬프트 상황을 갖고 있다면 0이 아닌 값을 반환한다. 18. 9절 [Shift State] 참조.

함수 : int wctomb (char *string, wchar_t wchar)

wctomb("와이드 캐릭터를 다중 바이트로") 함수는 와이드 캐럭터 코드 wchar을 그에 해당하는 다중 바이트 시퀀스로 변환하고, 그 결과를 string의 주소에서 시작하는 바이트들에 저장한다. 아무리 많아도 MB_CUR_MAX 문자들이 저장된다.
 
널 종료문자가 없는 문자열을 가지고 호출된 wctomb 함수는 세 가지 가능성으로 구분한다: 유용한 와이드 캐릭터 코드( 다중 바이트 문자로 변환될 수 있는 것. ), 유용하지 않은 코드, 그리고 0. 유용한 코드가 주어진 wctomb는 그것은 다중 바이트 문자로 변환하고 string을 시작으로 바이트들을 저장한다. 그리고 그것은 그 문자의 바이트들의 개수를 반환한다( 적어도 한 개 이상하고, 결코 size보다 커서는 안된다. ).
 
만일 wchar이 유용하지 않은 와이드 캐릭터 코드라면, wctomb는 -1을 반환한다. 만일 wchar이 0이라면, 그것은 0을 반환하고, 또한 *string에도 0을 저장한다.
 
만일 다중 바이트 문자 코드가 쉬프트 문자들을 사용한다면, wctomb는 그것을 조사해서 쉬프트 상황을 갱신하고 유지한다. 만일 당신이 string으로 널 포인터를 사용해서 wctomb를 호출한다면, 표준 초기값으로 쉬프트 상황을 초기화한다. 만일 사용하고 있는 다중 바이트 문자 코드가 실제로 쉬프트 상황을 갖는다면 0이 아닌 값을 반환한다. 18. 9절 [Shift State] 참조.
 
wchar에 0을 넣고, string이 null이 아닌 것을 가지고 이 함수를 호출하면, 다중 바이트 문자 0을 저장하고 0을 반환하는것은 물론이고 저장된 쉬프트 상황을 다시 초기화하는 부작용을 갖는다.


18. 8 문자 대 문자 변환의 예제

기술자 input 으로부터 다중 바이트 문자 텍스트를 읽어서 그에 해당하는 와이드 캐럭터들을 기술자 output에 쓰는 예제 프로그램이다. 우리가 이 예제에서 일대일로 문자들을 변환하려한 것은 mbstowcs 함수가 널 문자가 있다면 변환을 계속하지 않고, 읽어들인 많은 입력중에 부분적으로 유용하지 않은 문자가 있다면 이것에 완전하게 대처할 수 없기 때문이다.

int
file_mbstowcs (int input, int output)
{
char buffer[BUFSIZ + MB_LEN_MAX];
int filled = 0;
int eof = 0;
 
while (!eof)
{
int nread;
int nwrite;
char *inp = buffer;
wchar_t outbuf[BUFSIZ];
wchar_t *outp = outbuf;
 
/* 입력 파일로부터 버퍼를 채운다. */
nread = read (input, buffer + filled, BUFSIZ);
if (nread < 0) {
perror ("read");
return 0;
}
/* 만일 파일의 끝에 도달하면, 더 이상 읽지 않도록 기록하라*/
if (nread == 0)
eof = 1;
 
/* filled는 버퍼에 있는 바이트의 개수이다. */
filled += nread;
 
/* 그들 바이트들을 우리가 할 수 있는 한 많이 와이드 캐릭터로 변환하라. */
while (1)
{
int thislen = mbtowc (outp, inp, filled);
/* 유용하지 않은 문자에서 변환을 멈춰라; 이것은 유용한 문자의 단지 첫 번째 부분을 읽었음을 의미한다. */
if (thislen == -1)
break;
/* 널 문자를 다른 문자처럼 취급하라, 그러나 또한 쉬프트 상황을 재설정하라. */
if (thislen == 0) {
thislen = 1;
mbtowc (NULL, NULL, 0);
}
/* 이 문자를 지나서 진행하라. */
inp += thislen;
filled -= thislen;
outp++;
}
 
/* 우리가 만들었던 와이드 캐럭터들을 기록하라 */
nwrite = write (output, outbuf, (outp - outbuf) * sizeof (wchar_t));
if (nwrite < 0)
{
perror ("write");
return 0;
}
 
/* 만일 우리가 실제 유용하지 않은 문자를 가졌는지 보자. */
if((eof && filled > 0) || filled >= MB_CUR_MAX) {
error ("invalid multibyte character");
return 0;
}
 
/* 만일 어느 문자들이 앞에서 온것임에 틀림없다면, 버퍼의 처음에 그들을 저장하라. */
if (filled > 0)
memcpy (inp, buffer, filled);
}
}
return 1;
}


18. 9 쉬프트 시퀀스들을 사용한 다중 바이트 코드들

어떤 다중 바이트 문자 코드에서, 어느 특정한 바이트 시퀀스의 의미가 고정되지 않은 것이 있다. 그 의미는 동일한 문자열의 처음에 있는 어떤 시퀀스에 의존한다. 전형적으로 그것은 단지 몇 개의 시퀀스로서 다른 시퀀스들의 의미를 변화시킬수 있다; 그들을 쉬프트 시퀀스라고 부르고 우리는 그 다음에 나타나는 다른 시퀀스를 위해서 쉬프트 시퀀스를 설정한다고 말한다.

쉬프트 상황과 쉬프트 시퀀스를 설명하기 위해서, 우리는 시퀀스 0200이 0240에서 0377의 범위를 갖는 바이트의 쌍으로 한 문자를 나타내는 일본어 모드를 시작함을 나타내고, 시퀀스 0201이 0240에서 0377의 범위를 갖고 ISO Latin-1의 문자 집합에 따라 해석되는 Latin-1 모드를 시작함을 나타낸다고 가정하자. 이것은 두개의 선택 가능한 쉬프트 상황을 가진 다중 바이트 코드("일본어 모드"와 "Latin-1 모드")를 가지고, 두 개의 쉬프트 시퀀스는 특정한 쉬프트 상황들을 설정한다. 사용하고 있는 다중 바이트 문자 코드가 쉬프트 상황들을 사용할 때, mblen, mbtowc 그리고 wctomb는 문자열을 조사해서 현재의 쉬프트 상황을 갱신하고 유지한다. 이러한 작업을 적당하게 만들기 위해서, 당신은 다음의 규칙들을 따라야만 한다.

문자열에 대한 조사를 시작하기 전에, 다중 바이트 문자 주소를 널 포인터로 해서 그 함수를 호출하라_예를 들어, mblen(NULL, 0). 이것은 표준 초기값으로 쉬프트 상황을 초기화한다.

한 번에 한문자씩, 순서대로 문자열을 조사하라. "뒤로"가지 말고 이미 조사했던 문자들을 다시 조사하지 말고, 다른 문자열을 처리하는 동안 방해하지 말아라.

이들 규칙들을 따라 mblen을 사용하는 예제가 있다.

void
scan_string (char *s)
{
int length = strlen (s);
 
/* 쉬프트 상황을 초기화하라. */
mblen (NULL, 0);
 
while (1) {
int thischar = mblen (s, length);
/* 문자열의 끝과 유용하지 않은 문자들을 처리하라 */
if (thischar == 0)
break;
if (thischar == -1) {
    error ("invalid multibyte character");
    break;
}
/* 이 문자를 넘어서 진행하라 */
s += thischar;
length -= thischar;
}
}

함수 mblen, mbtowc 그리고 wctomb들은 사용하고 있는 다중 바이트 코드가 쉬프트 상황을 사용할 때 재진입하지 않는다. 그렇지만, 어느 다른 라이브러리 함수도 이들 함수들을 호출하지 않기 때문에 당신은 그 쉬프트 상황이 당신이 이해할 수 없게 변할것이라고 걱정할 필요가 없다.


목차 이전 : 17. 날짜와 시간 다음 : 19. 지역과 세계화

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

21. 신호처리(Signal)  (0) 2007.12.22
19. 지역과 세계화  (0) 2007.12.22
18. 확장된 문자  (0) 2007.12.22
17. 날짜와 시간  (0) 2007.12.22
16. 형식일치( Pattern Matching )  (0) 2007.12.22
15. 탐색과 정렬  (0) 2007.12.22
Comment 0 Trackback 0
Top

17. 날짜와 시간

목차 이전 : 16. 패턴 매칭 다음 : 18. 확장된 문자들


17 날짜와 시간

이 절은 현재의 시간과, 다르게 시간을 표현하는 방법들 사이의 전환을 결정하는 함수들을 포함하여, 날짜와 시간들을 위한 함수들을 설명하고 있다.

시간 함수들은 세 가지 주요한 범주로 나뉘어진다.

경과된 CPU 시간을 계산하는 함수들은 17. 1절 [Processor Time] 에서 논의되었다.

절대 시각이나 달력 시간을 계산하는 함수들은 17. 2절 [Calendar Time] 에서 논의되었다.

알람과 타이머를 설정하는 함수들은 17. 3절 [Setting an Alarm] 에서 논의되었다.


17. 1 프로세서 타임

만일 당신이 당신의 프로그램을 최적화 하거나, 또는 그 효율성을 계산하려한다면, 어느 주어진 지점이후에, 얼마나 많은 프로세서 시간과 CPU 시간이 흘렀는지를 알 수 있도록 하는 유용한 방법이 있다. 프로세서 시간은 벽시계와는 다른데, 그것은 벽시계가 입/출력이나 다른 어떤 프로세스가 동작하고 있는 동안 얼마나 시간이 흘렀는지를 알리지 않기 때문이다. 프로세서 시간은 데이터 타입 clock_t로 표현되고, 단일한 프로그램의 시작점에서 표시된 어떤 기준 시간과 연관된 시간의 흐름을 숫자로 표현한다.

17. 1. 1 기준 CPU 시간 조사

프로세스에 의해 사용된, 경과된 CPU 시간을 얻기 위해서는, 당신은 clock함수를 사용할 수 있다. 이 함수는 헤더파일 'time. h'에 선언되어 있다.

특별한 사용 예로, 당신이 시작점과 끝점 사이의 시간의 간격을 구하려면, clock함수를 호출하고, 그 값들을 나중 값에서 처음 값을 빼고, 그리고 CLOCKS_PER_SEC로 나누어라( 초당 횟수(clock ticks)의 숫자를 얻기 위해서 ), 다음 프로그램처럼.

#include <time. h>
 
clock_t start, end;
double elapsed;
 
start = clock();
. . .
/* 먼저 일을 시작하기 전에 clock함수를 호출한 다음 당신이 경과된 시간을 얻기 원하는 그 일을 시작하라 */
end = clock();
elapsed = ((double) (end - start)) / CLOCKS_PER_SEC;

다른 컴퓨터와 운영체제에서 어떻게 프로세서 시간을 기억하는지는 매우 광범위한 문제이다. 100분의 1초나, 100만분의 1초 사이의 내부프로세스 시계를 갖는 것이 보통이다.

GNU 시스템에서 clock_t는 long int 와 동등하고, CLOCKS_PER_SEC는 정수값이다. 그러나 다른 시스템에서는, clock_t는 정수이고, 매크로 CLOCKS_PER_SEC는 플로팅-포인트 형으로 서로 다르다. 어떤 시스템 하에 놓여있든지 상관없이 적당하게 작업을 수행하고, 산술적으로 그 명령을 확인하기 위해서는 아래의 예처럼 double형으로 프로세스 타임을 cast연산을 통해서 형변환을 하라.

매크로 : int CLOCKS__PER__SEC

이 매크로는 clock 함수에 의해 계산된 초당 시간주기(clock ticks)의 수를 나타내는 값이다.

매크로 : int CLK__TCK

이것은 옛날에 CLOCKS_PER_SEC 대신에 쓰이던 것이다.

데이터타입 : clock__t

이것은 clock함수에 의해 반환된 값의 형으로 시간주기의 단위이다.

함수 : clock_t clock(void)

이 함수는 경과된 프로세서 시간을 반환한다. 기준 시간은 항상 다르지만, 단일 프로세스 안에서는 변경되지 않는다. 만일 그 프로세서 시간이 유용하지 않거나 표현될 수 없다면, clock은 (clock_t)(-1)의 값을 반환한다.

17. 1. 2 자세하게 경과된 CPU 시간에 대한 조사

times 함수는 경과된 프로세서 시간에 대해서 struct tms오브젝트로 더 상세한 정보를 반환한다. 이 함수는 헤더파일 `sys'times. h'에 선언되어 있다.

데이터 타입 : struct tms

tms구조체는 프로세스 시간들에 대한 정보를 반환하는데 사용된다. 그것은 적어도 다음과 같은 멤버들을 포함하고 있다.

clock_t tms_utime

이것은 호출된 프로세스의 명령들을 수행하는데 사용된 CPU 시간이다.

clock_t tms_stime

이것은 호출된 프로세스 때문에 시스템에서 사용된 CPU 시간이다.

clock_t tms_cutime

이것은 wait 또는, waitpid에 의해 부모 프로세스에게 보고된 상황으로, 호출된 프로세스의 모든 종료된 자식 프로세스들의 tms_utime 과 tms_cutime의 값들을 합한 값이다; 23. 6절 [Process Completion] 참조. 즉, 호출한 프로세스의 모든 종료된 자식 프로세스 의 명령을 수행하는데 사용된 총 CPU 시간을 나타낸다.

clock_t tms_cstime

이것은 tms_cutime과 유사하지만, 호출된 프로세스의 모든 종료된 자식 프로세스들을 위하여 시스템에서 사용된 총 CPU 시간을 나타낸다.

모든 시간들은 시간주기(clock ticks)로 주어진다. 이들은 절대값이다; 새로이 만들어진 프로세스에서, 그들은 모두 0이다. 23. 4 [Creatina a Precess] 참조.

함수 : clock_t times (struct tms *buffer)

times 함수는 호출된 프로세스를 위한 프로세서 시간 정보를 buffer에 저장한다. 반환값은 clock()의 값과 동일하다: 어떤 기준 시간에서 실제로 경과된 시간. 기준 시간은 어떤 특별한 프로세스 상에서는 변하지 않는 상수 값이고, 시스템 시작시간과 연관된 시간으로 표현한다. 실패를 하면 (clock_t)(-1)의 값을 반환하다.
이식성 노트: 17. 1. 1절 [Basic CPU Time] 233에서 설명된 clock 함수는, ANSI C 표준함수이고, times 함수는 POSIX. 1의 함수이다. GNU 시스템에서는, clock함수로 표현된 값은 times에 의해 반환된 tms_utime 과 tms_stime의 합계와 동등한 값이다.


17. 2 달력 시간

이 절은 그레고리력에 의한 날짜와 시간들을 기억하기 위한 도구들을 설명하고 있다.

날짜와 시간 정보를 위한 세 가지 표현방법이다.

달력 시간( 데이터 타입 time_t)은 간단히 어떤 정해진 기준시간 이후 몇초가 흘렀는지를 숫자로 표현한다.

또한 초를 미세하게 나누어서 표현하는 고해상도 시간 표현이 있다 (데이터 타입 struct timeval). 당신이 큰 정밀도를 필요로 할 때 보통의 달력 시간대신 이 시간 표현을 사용하라.

지역 시간이나 broken-down time( 데이터타입 struct tm)은 정해진 시간 구역에서 정해진 년, 달, 등등의 구성요소들의 집합으로 날짜와 시간을 표현한다. 이 시간 표현은 보통 형식화된 날짜와 시간 값들을 결합하는데 사용된다.

 

17. 2. 1 간단한 달력 시간

이 절은 달력 시간을 표현하기 위한 데이터 타입 time_t와 달력 시간 오브젝트에서 동작하는 함수들을 설명한다. 이 도구들은 헤더파일 `time. h'에 선언되어 있다.

데이터 타입 : time_t

이것은 달력 시간을 표현하기 위해서 사용하는 데이터 타입이다. GNU C 라이브러리와 다른 POSIX-계열에서, time_t는 long int 와 같다. 절대시간 값으로 해석될 때, 그것은 협정 세계시간 1970년 1월 1일 00: 00: 00 시간 이후 경과된 초의 수를 표현한다. ( 이 날짜는 때때로 시대(epoch)로써 사용된다. ) 다른 시스템에서, time_t는 정수이거나 플로팅-포인트이거나 할 것이다.

함수 : double difftime (time_t time1, time_t time0)

difftime 함수는 double 형으로 time1과 time2 사이의 경과된 시간을 초로 반환한다. GNU 시스템에서, 당신은 간단히 time_t 값들을 빼는 것으로 그 값을 구할 수 있지만, 다른 시스템에서는 time_t 데이터 타입은 직접적으로 빼기를 할 수가 없게, 기호화 되어있을 것이다.

함수 : time_t time (time_t *result)

time 함수는 time_t의 형으로 표현된 값으로 현재의 시간을 반환한다. 만일 인수 result가 널 포인터가 아니라면, time값은 *result에 저장되어진다. 만일 달력 시간이 유용하지 않다면, (time_t)(-1)이 반환된다.

 

17. 2. 2 고해상도 달력

time_t 데이터 타입은 오직 1초 사이의 해상도를 갖는 달력 시간들을 표현하기 위해 사용한다. 어떤 응용프로그램은 더 정밀할 필요가 있다.

그래서, GNU C 라이브러리에서는 1초 보다 더 높은 고해상도의 달력시간을 표현할 용량이 있는 함수들을 갖고 있다. 이 절에서 설명하고 있는 함수들과 연관된 데이터 타입들은 `sys/time. h'에 선언되어 있다.

데이터 타입 : struct timeval

구조체 struct timeval은 달력 시간을 표현한다. 그것은 다음과 같은 멤버들을 갖고 있다.

long int tv_sec

이것은 epoch 이후를 초(second)로 표현한다. 이것은 보통 time_t 값과 동일하다.

long int tv_usec

이것은 마이크로초(microsecond)로 표현된 시간값이다. 어떤 시간 struct timeval 값들은 시간 간격들을 위한 사용자이고, 그러면 tv_sec 멤버는 간격에서 경과된 초(second) 이고, tv_usec는 부가적으로 흐른 시간 마이크로 초의 수이다.

데이터 타입 : struct timezone

구조체 struct timezone는 지역 시간대에 대한 최소의 정보를 저장하기 위해 사용된다. 그것은 다음의 멤버들을 갖는다.

int tz_minuteswest

이것은 그리니치 표준시(GMT)의 minutes west의 수이다.

int tz_dsttime

만일 0이 아니라면, 그 해(year)의 어떤 기간동안 일광 절약시간을 적용한다.

struct timezone 형은 절대값이고 결코 사용할일은 없을 것이다. 대신에 17. 2. 6절 [Time Zone Functions] 에서 설명된 도구들에서 사용한다.

struct timeval형의 두 값들을 빼는 것이 종종 필요하다. 이곳에는 이런 일을 하는 가장 좋은 방법이 있다. 그것은 tv_sec 멤버가 unsigned 형인 어떤 기묘한 운영체제하에서 조차도 돌아간다.

/* `struct timeval'의 형을 가진 값 X 와 Y를 빼고, 그 결과는 RESULT에 저장하라. 그 차이가 음수이면 1을 반환하고, 그렇지 않으면 0을 반환하라 */

int
timeval_subtract (result, x, y)
struct timeval *result, *x, *y;
{
/* 갱신된 y로 나중에 빼기를 하기 위해, y값을 가져와라 */
if (x->tv_usec < y->tv_usec) {
int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
y->tv_usec -= 1000000 * nsec;
y->tv_sec += nsec;
}
if (x->tv_usec - y->tv_usec > 1000000) {
int nsec = (y->tv_usec - x->tv_usec) / 1000000;
y->tv_usec += 1000000 * nsec;
y->tv_sec -= nsec;
}
 
/* 기다림을 위한 시간을 계산하라. tv_usec는 어떤 양의 값이다. */
result->tv_sec = x->tv_sec - y->tv_sec;
result->tv_usec = x->tv_usec - y->tv_usec;
 
/* 만일 결과가 음수이면 0을 반환하라 */
return x->tv_sec < y->tv_sec;
}

함수 : int gettimeofday (struct timeval *tp, struct timezone *tzp)

gettimeofday 함수는 tp 가 가리키고 있는 구조체 struct timeval에서 현재의 날짜와 시간을 반환한다. 그 시간대에 대한 정보는 tzp가 가리키고 있는 구조체 안에 반환된다. 만일 tzp 인수가 널 포인터라면, 시간 대 정보는 무시된다. 반환값이 0이면 성공이고, 실패면 -1이다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

ENOSYS

운영체제가 시간대 정보 얻는 것을 지원하지 않고, tzp가 널 포인터가 아니다. GNU 운영체제는 시간대 정보를 표현하기 위해서 struct timezone를 사용하는 것을 지원하지 않는다; 그것은 예전에 4. 3 BSD에서 사용되던 것이다. 대신에 17. 2. 6절[Time Zone Functions] 에서 설명된 도구들을 사용하라.

함수 : int settimeofday(const sruct timeval *tp, const struct timezone *tzp)

settimeofday 함수는 인수들에 따라서 현재의 날짜와 시간을 설정한다. gettimeofday를 통해서, 만일 tzp가 널 포인터라면, 시간대 정보는 무시된다. 당신이 settimeofday를 사용할려면 특권이 부여된 사용자이어야만 한다. 성공하면 반환값은 0이고, 실패하면 -1이다.
 
다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EPERM 이 프로세스가 특권이 없기 때문에 시간을 설정할 수 없다.

ENOSYS 운영체제가 시간대 정보를 설정하는 것을 지원하지 않고 tzp가 널 포인터가 아니다.

함수 : int adjtime (const struct timeval *delta, struct timeval *olddelta)

현재의 시간을 점차적으로 조정하기 위해서 시스템 시계를 빠르게, 또는 느리게 하는 함수이다. 만일 간단히 현재의 시간을 설정할 수 없는, 항상 천편일률적으로 증가하기만 하는 시스템 시계라면, 이것은 그 시간을 맞출 수가 있다.
 
delta 인수는 현재의 시간을 위한 조정을 지정한다. 만일 음수라면 시스템 시계는 잃어버린 많은 시간을 복구할때까지 천천히 내려가고, 만일 양수라면, 시스템 시계는 천천히 올라간다. 만일 olddelta 인수가 널 포인터가 아니라면, adjtime함수는 조정이 아직 수행되기 전의 앞의 시간에 대한 정보를 반환한다. 이 함수는 특별히 로컬 네트웍상의 컴퓨터들의 시간을 동기화 하는데 사용된다.
 
당신은 그것을 사용하기 위해서는 특권이 부여된 사용지가 되어야만 한다. 성공하면 0을 반환하고, 실패하면 -1을 반환한다.  
다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EPERM 당신은 시간을 설정할 수 있는 특권을 갖고 있지 않다.

 
이식성 노트 : gettimeofday, settimeofday, 그리고adjtime 함수들은 BSD로부터 왔다.

 

17. 2. 3 Broken-down 시간

달력 시간은 초단위로 표현된다. 이것은 계산하기는 편리하지만, 사람이 보통 날짜와 시간을 표현하는 방법과는 다르다. 그와 달리, broken-down 시간은 연도, 달, 날짜 등을 분리하여 2진으로 표현한다. Broken down 시간값은 계산에는 불편하지만, 그들은 인간이 읽을 수 있는 시간으로 유용하게 사용된다.

broken-down 시간값은 지역시간대의 선택과 항상 연관되어 있고, 그것은 항상 어떤 시간대가 사용되었음을 지적한다. 이 절에 있는 심볼들은 헤더파일 `time. h'에 선언되어 있다.

데이터 타입 : struct tm

이것은 broken-down 시간을 표현하기 위해 사용되는 데이터 타입이다. 그 구조체는 어느 순서로 나타날 수 있는, 적어도 다음과 같은 멤버들을 포함하고 있다.

int tm_sec

이것은 0에서 59까지의 범위를 갖는 초(second)를 표현하고 있다. (실제로는 "윤초"를 허용해서, 61까지가 제한이다. )

int tm_min

이것은 0에서 59까지의 범위를 갖는 분(minute)을 나타낸다.

int tm_hour

이것은 0에서 23까지의 범위를 갖는 시(hours)를 나타낸다.

int tm_mday

이것은 1에서 31까지의 범위를 갖는 달 중의 그날을 나타낸다.

int tm_mon

이것은 0에서 11까지의 범위를 갖는, 1월 이후의 달의 수를 표현한다.

int tm_year

이것은 1900년 이후의 년(year)의 수이다.

int tm_wday

이것은 0에서 6의 범위를 갖는, 일요일을 기점으로 해서 지난날의 수를 나타낸다.

int tm_yday

이것은 0에서 365의 범위를 갖는, 1월 1일 이후 지난날의 수를 나타낸다.

int tm_isdst

이것은 시간을 표현하는데, 일광절약 시간이 영향을 주는지(있었거나, 또는 있을 예정이거나)를 지적하는 플래그이다. 그 값은 만일 일광 절약시간이 영향을 미치면 양수이고, 만일 그렇지 않으면 0이고, 그 정보가 음수이면 유용하지 않다.

long int tm_gmtoff

이 필드는 broken-down 시간값을 계산하는데 사용되어지는 시간대를 나타낸다; 그것은 초의 단위로 그리니치 표준시(GMT)를 얻어서, 그것에 지역시간을 얻기 위해서 더해져야만 하는 양( amount)이다. 그 값은 변화 가능한 시간대( 17. 2. 6절 [Time Zone Functions] 참조. )와 같은 것이다. 당신은 또한 그리니치 표준시의 "seconds west의 수"라고 생각할 수도 있다. tm_gmtoff 필드는 GNU 라이브러리 확장이다.

const char *tm_zone

이것은 broken-down 시간값을 계산하는데 사용되어지는 시간대를 위한 세-문자 이름이다. 이것은 GNU 라이브러리 확장이다.

함수 : struct tm *localtime (const time_t *time)

localtime 함수는 time이 가리키고 있는 달력시간을 사용자가 정한 시간대와 맞추어서, broken-down 시간으로 변환한다. 반환값은 어느 날짜와 시간 함수들을 호출해서 얻은 정적 broken-down 시간 구조체를 가리키고 있는 포인터이다. (그러나 어느 다른 라이브러리 함수도 이 오브젝트의 내용에 덧씌우기 (overwrite)를 할 수 없다. ) localtime을 호출하는 것은 다른 하나의 영향이 있다: 그것은 변수 tzname를 현재 시간대에 대한 정보로 설정한다. 17. 2. 6절 [Time Zone Functions] 참조.

함수 : struct tm *gmtime(const time_t *time)

이 함수는 broen-down이 세계 협정시간으로 표현된다는 것을 제외하고, localtime과 유사하다. _그리니치 표준시가 지역 시간대와 더 연관이 있다. 달력 시간들은 항상 세계 협정시로 표현된다는 것을 기억하라.

함수 : time_t mktime(struct tm *brokentime)

mktime 함수는 broken-down 시간 구조체를 달력시간 표현으로 변환시키기 위해서 사용된다. 그것은 다른 날짜에 기초하여 연도(year), 주(week)의 날수와 시간 요소들을 채워서 broken-down 구조체의 내용을 "일반화"한다.
 
mktime함수는 broken-down 시간 구조체의 멤버인 tm_wday와 tm_yday의 정해진 내용들을 무시한다. 그것은 달력 시간을 계산하기 위해서 다른 구성요소의 값들을 사용한다; 그것은 그들의 보통 범위의 밖에 있는 비일반화된 값들을 갖는 그들 구성요소들을 위해서 허용되었다. mktime이 마지막으로 하는 일은 brokentime 구조체의 구성요소들을 조정하는 것이다( tm-wday 와 tm-yday를 포함해서 ). 만일 정해진 broken-down 시간이 달력 시간으로 표현될 수 없다면, mktime은 (time_t)(-1)의 값을 반환하고 brokentime의 내용을 갱신할 수 없다. 호출된 mktime은 또한 현재의 시간대에 대한 정보로 변수 tzname 를 설정한다. 17. 2. 6절 [Time Zone Functions] 참조.

 

17. 2. 4 형식화된 날짜와 시간

이 절에 설명된 함수들은 문자열로 시간값들을 형식화한다. 이들 함수들은 헤더파일 'time. h'에 선언되어 있다.

함수 : char *asctime(const struct tm *brokentime)

asctime 함수는 brokentime이 가리키고 있는 broken-down 시간을 표준 형식을 가진 문자열로 변환한다.
"Tue May 21 13: 46: 22 1991\n"
주안에 있는 요일의 약자는: `Sun', `Mon', `Tue', `Wed', `Thu' `Fri' 그리고 `Sat' 이다.
달들의 약자는: '`Jan', `Feb', 'Mar', 'Apr', 'May', Jun', 'Jul', 'Aug', 'Sep', 'Oct', Nov', 그리고'Dec' 이다.
 
반환값은 어떤 날짜와 시간 함수들의 호출에 의한 결과로 덮어쓰기가 된, 정적으로 할당된 문자열을 가리킨다. (그러나 어느 다른 함수도 이 문자열의 내용에 덧씌우기를 할 수 없다. )

함수 : char *ctime(const time_t *time)

ctime함수는 시간값이 달력시간(지역시간) 형식으로 지정되었다는 것을 제외하고는, asctime과 유사하다. 그것은 asctime(localtime (time))과 같다. ctime는 localtime으로 하기때문에 변수 tzname을 설정한다. 17. 2. 6절 [Time Zone Function] 참조.

함수 : size_t strftime(char *s, size_t size, const char *template, const struct tm *brokentime)

이 함수는 sprintf 함수(7. 11절 [Formatted Input] 참조)와 유사하지만, 형식 템플릿 template안에 나타날 수 있는 변환지정은 시간 변환을 위해서 정해진 현재의 지역시간에 따른 brokentime의 날짜와 시간의 요소들을 프린트하도록 특별화되었다. (19장 [Locales] 참조. ) template안에 나타난 보통의 문자들은 출력 문자열 s에 복사되어 진다; 이것은 다중 바이트 문자들로 이루어진 열들을 포함할 수 있다. 변환지정자는 `%'문자 다음에 나타나고, 다음처럼 출력 문자열을 조정한다.
 

%a : 현재의 지역에 맞는 약자로 표현된 요일이름.

%A : 현재지역에 맞는 완전한 요일이름.

%b : 현재지역에 맞는 약자인 달 이름.

%B : 현재지역에 맞는 완전한 달 이름.

%c : 현재 지역을 위해 선택된 날짜와 시간 표현.

%d : 십진수로 표현된(01에서 31까지의 범위) 한달 안의 날짜.

%H : 24시간 시계를 사용해서(00에서 23까지의 범위), 십진수로 나타낸 시간.

%I : 12시간 시계를 사용해서(01에서 12까지의 범위), 십진수로 나타낸 시간.

%j : 십진수(001에서 366까지의 범위)로 표현된 일년안의 날수.

%m : 십진수(01에서 12까지의 범위)로 표현된 달.

%M : 십진수로 표현된 분.

%p : 주어진 시간값에 맞는, `am'또는 `pm' 또는 현재지역에 맞는 연관된 문자열.

%S : 십진수로 표현된 초.

%U : 첫 번째 주의 첫 번째 날인 첫 번째 일요일을 기점으로 해서, 십진수로 표현된 올해의 주의수

%W : 첫 번째 주의 첫 번째날인 첫 번째 월요일을 기점으로 해서, 십진수로 표현된 올해의 주의 수

%w : 일요일을 0으로 한, 십진수로 표현된 요일.

%x : 시간 없이, 현재지역을 위해 예약된 날짜 표현.

%X : 날짜 없이, 현재지역을 위해 예약된 시간 표현.

%y : 세기(century)없이(00에서 99까지의 범위), 십진수로 표현된 연도.

%Y : 세기를 포함해서, 십진수로 표현된 연도.

%Z : 시간대나 이름 또는 약자(시간대가 결정될 수 없다면 비워라)

%% : 문자 `%'

 
size 인수는 널 종료문자를 포함해서, 배열 s에 저장된 문자들의 최대 개수를 나타내기 위해서 사용될 수 있다. 만일 형식화된 시간이 size 크기보다 더 많은 문자들이 필요하다면, 초과되는 문자들은 버려진다. strtime을 통한 반환값은 널 종료문자를 포함하지 않은, 배열 s에 저장된 문자들의 개수이다. 만일 value가 size와 같다면, 그 배열이 너무 작았음을 의미한다; 당신은 배열을 크기를 크게 하고, strtime를 재 호출하라. 만일 s가 널 포인터라면, strftime은 실제로 아무 것도 쓰지 않지만, 대신에 쓰여졌던 문자들의 개수를 반환한다. strftime에 대한 예는 17. 2. 7절 [Time Functions Example] 참조.

 

17. 2. 5 TZ으로 시간대를 정하기

GNU 시스템에서, 사용자는 TZ 환경변수로 시간대를 정할 수 있다. 환경변수를 어떻게 설정하는가에 대한 정보는, 22. 2절 [Environment Variables] 참조. 시간대를 억세스하기 위한 함수는 'time. h'에 선언되어 있다. TZ 변수의 값은 세 가지 형식중에 하나가 된다. 첫 번째 형식은 지역시간대에서 일광절약시간(또는 썸머타임)이 없는 곳에서 사용된다:

std offset

std 문자열은 시간대의 이름을 정한다. 그것은 세 개 이상의 문자열의 길이를 가져야만 하고, 콜론이 선행되거나, 숫자, 콤마, 또는 플러스나 마이너스 부호가 들어가 있으면 안 된다. 지역시간대 이름 사이에 공백이 없어야한다. 이러한 제한들은 정확하게 그 지정을 분석하기 위해 필요하다. offset는 협정 세계시간을 얻기 위하여 지역시간에 더해야만 하는 시간값을 정한다. 그것은 [+|-]hh[: mm[: ss]]와 같은 구문이다. 이것은 만일 지역시간대가 그리니치 자오선(Prime Meridian)의 서쪽에 있다면 양수이고, 동쪽에 있다면 음수이다. 시간은 0과 24의 사이에 있어야만 하고, 분과 초는 0과 59의 사이에 있어야만 한다.
 
예를 들어, 일광절약시간을 택하지 않고, 동부표준시간을 어떻게 정하는지는 다음과 같다.
 
EST+5
일광절약시간이 있는 곳에서 사용되는 두 번째 형식:

std offset dst [offset], start[/time], end[/time]

처음의 std 와 offset은 위에 설명된 것처럼 표준시간 대를 정한다. dst 문자열과 offset은 일광절약시간의 시간대에 맞는 name과 offset을 정한다; 만일 offset을 생략하면, 디폴트로 표준시간보다 한시간 앞으로 정해진다.
지정자의 나머지는 일광절약시간이 있는 곳에서의 설명이다. 일광절약 시간이 영향을 미칠 때는 시작필드에 있고, 다시 표준시간으로 변환되었을 때는 끝 필드에 있다. 다음 형식들은 이들 필드를 위한 설명이다.

Jn

이것은 1과 365사이에 있는 어떤 날 n으로, 율리우스력의 날짜를 정한다. 윤년에 있는 2월 29일은 더해지지 않는다.

n

0과 365사이에 있는 어떤 날 n으로, 율리우스력의 날짜를 정한다. 윤년에 있는 2월 29일은 더해진다.

Mm. w. d

m달의 w주의 d날. 날 d는 0(일요일)과 6사이의 값이어야 한다. 주 w는 반드시 1과 5사이이다; 주 1은 그 달에서 날 d가 발생한 첫 번째 주이고, 주 5는 달에서 마지막날 d가 있는 마지막 주이다. 달 m은 1과 12사이이다. time필드들은 지역시간을 다른 시간표현으로 변환할 때 지정한다. 만일 생략되면, 디폴트값은 02: 00: 00이다. 예를 들어, 미국에서 적당한 일광절약시간이 포함된 날짜로 동부시간대를 지정하는 것이 있다. 보통 GMT로부터의 offset는 5시간이다; 그리니치 자오선으로부터 서쪽으로 있기 때문에 양수 값이다. 썸머타임은 4월 첫 번째 일요일 오전 2시에 시작되고, 10월 마지막 일요일 오전 2시에 끝난다.

EST+5EDT, M4. 1. 0/M10. 5. 0

특별한 어느 지역에서 일광절약시간의 예정은 해마다 변경된다. 정확하게 하려면, 그 지역에 맞는 일광절약시간의 예정에 기초하여 날짜와 시간을 변경해야한다. 그렇지만, 시스템은 그 예정이 어떻게 변경되었는지를 당신이 지정할 도구들을 가지고 있지 않다. 당신이 이것을 할 수 있는 가장 좋은 방법은 하나의 특별한 예정을 지정해서 하는 것이다. 보통 현재의 시간을 표시하고 아무런 문제가 없을 때, 이것은 다른 날짜로 변경하기 위해 사용된다.

세 번째 형식은 이와 같다.

: characters

각각의 운영체제마다 이 형식을 다르게 해석한다. GNU C 라이브러리에서는, 문자들은 시간대를 표현하는 파일의 이름으로 해석한다. 만일 TZ 환경변수가 아무런 값도 가지고 있지 않다면, 그 오퍼레이션은 디폴트로 시간대를 정한다. 각 운영체제는 디폴트시간대를 정하기 위한 자신 나름대로의 규칙을 갖고 있고, 그래서 우리가 그들에 대해서 말할 수 있는 것은 적다.

 

17. 2. 6 시간대를 위한 함수와 변수들

변수 : char *tzname[2]

tzname 배열은 사용자가 선택한 표준 시간대와 일광절약시간대의 이름들을 표준 세-문자로 가진 두 개의 문자열을 저장한다. tzname[0]은 표준시간대(예를 들어 "EST")의 이름이고, tzname[1]은 일광절약시간이 사용될 때 시간대를 위한 이름이다(예를 들어, "EDT"). 이들은 TZ 환경변수로부터 std 와 dst문자열과 같다. tzname 배열은 tzset, ctime, strftime, mktime, 또는 localtime이 호출될때마다 TZ 환경변수로 초기화된다.

함수 : void tzset(void)

tzset함수는 TZ 환경변수의 값으로 tzname변수를 초기화한다. 이것은 시간대에 의존하는 다른 시간 변환 함수들에서 사용될 때 자동적으로 호출되기 때문에 당신이 직접 당신의 프로그램을 통해서 이것을 부를 필요가 없을 것이다.

다음의 변수들은 유닉스 시스템 V와의 호환성을 위해서 정의되었다. 이들 변수들을 localtime 함수를 호출함으로써 설정된다.

변수 : longint timezone

이것은 그리니치 표준시와 지역 표준 시간사이의 차이를 초단위로 저장한다. 예를 들어, 미국에서 동부시간대의 값은 5*60*60이다.
역자주 : 아까 동부시간대가 그리니치 표준시와 5시간의 차이가 난다고 했으니까. . . 5*60*60.

변수 : int daylight

이 변수는 만일 표준 미국 일광절약시간 규칙이 적용된다면 0이 아닌 값을 가진다.

 

17. 2. 7 시간 함수들의 예제

지역시간과 달력시간 함수들의 몇가지를 사용하는 예를 보여주는 프로그램이다.

#include <time. h>
#include <stdio. h>
 
#define SIZE 256
 
int
main (void)
{
char buffer[SIZE];
time_t curtime;
struct tm *loctime;
 
/* 현재의 시간을 얻어라 */
curtime = time (NULL);
 
/* 지역시간 표현으로 변환하라 */
loctime = localtime (&curtime);
 
/* 표준 형식으로 날짜와 시간을 출력하라. */
fputs (asctime (loctime), stdout);
 
/* 보기 좋은 형식으로 그것을 출력하라 */
strftime (buffer, SIZE, "Today is %A, %B %d. \n", loctime);
fputs (buffer, stdout);
strftime (buffer, SIZE, "The time is %I: %M %p. \n", loctime);
fputs (buffer, stdout);
return 0;
}
 
다음과 같은 출력을 만들어낸다.
 
Wed Jul 31 13: 02: 36 1991
Today is Wednesday, July 31.
The time is 01: 02 PM.


17. 3 알람을 설정하기

alarm 과 setitimer 함수들은 어떤 미래의 시간에 프로세스 그 자체에 인터럽트를 거는 메커니즘을 제공한다. 그들은 타이머를 설정함으로써 이루어진다. 타이머가 끝나면, 프로세스는 신호를 받는다.

각 프로세스는 유용한 세 가지 독립 간격 타이머를 갖는다.

시계 시간을 계산하는 실제-시간 타이머. 이 타이머는 그 시간이 경과되면 프로세스에게 SIGALRM 신호를 보낸다.

프로세스에 의해 사용된 CPU시간을 셈하는 가상 타이머. 이 타이머는 그 시간이 경과되면 프로세스에게 SIGVTALRM 신호를 보낸다.

profiling 타이머는 프로세스에 의해 사용된 CPU 시간과 프로세스를 위한 시스템 호출에서 사용된 CPU 시간 양쪽을 계산한다.

이 타이머는 그 시간이 경과되었을 때, 프로세스에게 SIGPROF 신호를 보낸다.

당신은 어떤 종류로 설정된 어느 주어진 시간에 한가지의 타이머만을 사용할 수 있다. 만일 당신이 아직 경과되지 않은 시간을 가진 타이머를 설정하면, 그 타이머는 새로운 값으로 간단히 재설정된다. 당신은 setitimer 이나 alarm을 호출하기 전에 signal이나 sigaction을 사용해서 적당한 알람 신호를 위한 핸들러를 만들어야 할 것이다. 그렇지 않다면, 시간이 경과되었을 때 발생할 수 있는 사건들의 연결은 알람 신호들에 대한 디폴트 동작으로, 프로그램의 종결을 발생시킬 것이다. 21장. [Signal Handling] 참조.

setitimer 함수는 알람을 설정하기 위해서 주요한 방법이다. 이 도구는 헤더파일 `sys/time. h'에 선언되어 있다. 실제-시간 타이머를 설정하기 위해서 간단한 인터페이스를 제공하는 alarm 함수는 `unistd. h'에 선언되어 있다.

데이터타입 : struct itimerval

이 구조체는 타이머가 경과되는 때는 정하기 위해서 사용되어진다. 그것은 다음과 같은 멤버들을 포함하고 있다.

struct timeval it_interval

이것은 연속적인 타이머 인터럽트들 사이의 간격이다. 만일 0이면, alarm은 오직 한 번만 보내질것이다.

struct timeval it_value

이것은 첫 번째 타이머 인터럽트 간격이다. 만일 0이면, 알람은 불가능하다. 데이터타입 struct timeval은 17. 2. 2절 [High-Resolution Calendar] 에 선언되어 있다.

함수 : int setitimer(int which, struct itimerval *old, struct itimerval *new )

setitimer 함수는 new에 따른 것에 의해 정해진 타이머를 설정한다. which 인수는 ITIMER_REAL, ITIMER_VIRTUAL, 또는, ITIMER_PROF 중의 한 값을 가질 수 있다. 만일 old가 널 포인터가 아니라면, setitimer 은 그것이 가리키고 있는 구조체에 같은 종류의 전에 경과되지 않았던 어느 타이머에 대한 정보를 반환한다.
 
성공하면 반환값은 0이고, 실패하면 -1이다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EINVAL : 타이머의 간격이 너무 길었다.

함수 : int getitimer(int which, struct itimerval *old)

getitimer 함수는 old가 가리키고 있는 구조체에 의해 정해진 타이머에 대한 정보를 저장한다.
 
반환값과 에러상황들은 setitimer와 같다.

ITIMER_REAL

이 상수는 실제-시간 타이머로 정하기 위해서, setitimer와 getitimer 함수들에서 which 인수로써 사용될 수 있다.

ITIMER_VIRTUAL

이 상수는 가상 타이머로 정하기 위해서, setitimer와 getitimer 함수들에서 which 인수로써 사용될 수 있다.

ITIMER_PROF

이 상수는 profiling 타이머로 정하기 위해서, setitimer와 getitimer 함수들에서 which 인수로써 사용될 수 있다.

함수 : unsigned int alarm(unsigned int seconds)

alarm 함수는 초단위로 seconds가 경과되도록 실제-시간 타이머를 설정한다. 만일 당신이 현존하는 alarm을 취소하길 원한다면, 당신은 seconds인수를 0으로 해서 alarm을 호출하면 된다. 반환값은 미리 호출됐던 alarm에서 몇초가 남았는지를 가리킨다. 만일 미리 호출된 alarm이 없다면, alarm은 0을 반환한다. alarm함수는 이처럼 setitimer에 의하여 정의될 수 있다.
 
unsigned int alarm (unsigned int seconds)
{
struct itimerval old, new;
new. it_interval. tv_usec = 0;
new. it_interval. tv_sec = 0;
new. it_value. tv_usec = 0;
new. it_value. tv_sec = (long int) seconds;
if (setitimer (ITIMER_REAL, &new, &old) < 0)
return 0;
else
return old. it_value. tv_sec;
}
 
alarm 함수를 사용하는 예는 21. 4. 1절 [Handler Returns] 에서 보여주고 있다. 만일 당신이 주어진 시간(초단위의)동안 당신의 프로세스가 기다리기를 원한다면 sleep 함수를 사용할 수 있을 것이다. 17. 4절[Sleeping] 참조.
당신은 타이머가 경과되었을 때, 정확하게 도착된 신호를 셀 수 없을 것이다. 멀티프로세싱 환경에서는 어느 정도의 지연이 있기 때문이다.
 
이식성 노트: setitimer 와 getitimer 함수들은 BSD Unix로부터 왔고, alarm 함수는 POSIX. 1 표준으로 정해져있다. setitimer 은 alarm보다는 더 강력하지만, alarm이 더 광범위하게 쓰인다.


17. 4 Sleeping

sleep 함수는 짧은 시간동안 그 프로그램을 기다리게 하는 간단한 방법을 제공한다. 만일 당신의 프로그램이 신호들을 사용하지 않는다면( 종료를 제외하고), 당신은 sleep가 정해진 시간동안에 확실하게 기다린다는 것을 기대할 수 있다. 그렇지 않다면, sleep는 만일 신호가 도착한다면 곧바로 반환될 수 있다; 만일 당신이 신호에 상관없이 주어진 시간동안 기다리기를 원한다면 select( 8. 6절 [Waiting for I/O] 참조. )를 사용하고, 기다리는 어떠한 기술자도 지정하지 말아라.

함수 : unsigned int sleep (unsigned int seconds)

sleep 함수는 seconds동안 기다리거나, 첫 번째 발생한 신호가 도착될 때까지 기다린다. 만일 sleep 함수가 요청된 시간이 경과되었기 때문에 반환했다면, 반환값은 0이다. 만일 신호가 도착되어서 반환했다면, 그 반환값은 아직 경과되지 않고 남은 시간의 값이다. sleep 함수는 `unistd. h'에 선언되어 있다.
 
sleep가 0이 아닌 값을 반환했을 때, 그 반환값을 사용해서 정해진 시간동안 다시 기다리도록 sleep를 재호출하려는 유혹에 저항하라. 이것은 빈번하게 도착한 신호들만큼 정확하게 길어진 어떤 시간동안 작업할 것이다. 그러나 각 신호는 그 부가적인 시간(초)으로 인해서 원래 정해진 기다림의 시간보다 길어지게 할 것이다.
 
기다림이 얼마나 길어지거나, 짧아질 수 있는지에 대한 아무런 제한이 없는 어떤 신호들이 불행하게도 빠르게 연속적으로 발생한다고 가정하라. 대신에, 그 프로그램이 기다림을 멈추게 된 그 시간을 계산하고, 다시 남아있는 시간동안 기다림을 다시 시도하도록 하라. 이것은 1초보다도 더 많이 시간차이가 나지 않을 것이다. 더 적은 시간동안 작업하려면, 당신은 select를 사용하고, 꽤 정확한 시간동안 기다리게 할 수 있다. ( 물론, 멀티유저 시스템에서 그 기계가 단지 한 응용프로그램을 위해서 사용되지 않는다면, 피할 수 없는 부가적인 지연을 발생될 것이다, 당신이 그것을 피할 수 있는 방법은 아무 것도 없다. )
 
어떤 시스템상에서, 만일 당신의 프로그램에서 SIGALRM을 사용한다면 sleep는 이상하게 동작할 수 있다. 심지어 sleep가 호출되었을 때 SIGALRM 신호가 무시되거나 블록 된다면, sleep는 SIGALRM 신호 때문에 시기상조적으로 반환될 것이다. 만일 당신이 프로세스가 기다리고 있는(sleeping)동안 배달된 SIGALRM 신호와 SIGALRM 신호를 위한 핸들러를 만들었다면, 그 동작은 당신이 만든 신호 핸들러에 그 처리를 맡기는 대신에 sleep의 반환을 발생시킬 것이다. 그리고 만일 sleep가 alarm을 요청하거나 SIGALRM의 처리를 선택하는 핸들러가 가진 신호에 의해 인터럽트 된다면, 이 핸들러와 sleep는 방해될 것이다. GNU 시스템에서는, sleep가 SIGALRM에 의하여 작업하지 않기 때문에, sleep와 SIGALRM을 같은 프로그램에서 사용하는 것이 안전하다.


17. 5 자원 사용

함수 getrusage 와 데이터타입 struct rusage는 프로세스의 사용형태를 시험하기 위해서 사용된다. 그들은 `sys/resource. h'에 선언되어 있다.

함수 : int getrusage(int processes, struct rusage *rusage)

이 함수는 processes에 의해 정해진 그 프로세스를 위해 사용된 것을 *rusage에 정보를 저장하여, 보고한다. 대부분의 시스템에서, processes는 오직 두 개의 유용한 값을 가진다:

RUSAGE_SELF

단지 현재의 프로세스.

RUSAGE_CHILDREN

이미 종료된 모든 자식 프로세스(직접 과 간접).
GNU 시스템에서, 프로세스 ID를 정함으로 해서, 특정한 자식 프로세스에 대해서 조사할 수 있다. getrusage의 반환값은 성공하면 0이고, 실패하면 -1이다.

EINVAL

processes 인수가 유용하지 않다.

특정한 자식 프로세스를 위한 사용형태를 얻는 방법은 wait4 함수를 통해서 할 수 있는데, 그 함수는 자식 프로세스가 종료되었을 때 자식 프로세스를 위한 모든 것을 반환한다. 23. 8절 [BSD Wait Functions] 참조.

데이터타입 : struct rusage

이 데이터타입은 다양한 종류의 자원에 대한 사용량을 기록한다. 이것은 다음과 같은 멤버들을 갖으며, 다른 것도 가능하다.
 
struct timeval ru_utime : 사용된 사용자 시간.
struct timeval ru_stime : 사용된 시스템 시간.
long ru_majflt : 폴트의 개수.
long ru_inblock : 블록된 입력 명령의 개수.
long ru_oublock : 블록된 출력 명령의 개수.
long ru_msgsnd : 보냈던 메시지의 개수.
long ru_msgrcv : 받았던 메시지의 개수.
long ru_nsignals : 받았던 시그널의 개수.

사용형태를 시험하는 함수로는 vtimes도 있지만 여기서 설명하지는 않는다. 그것은 `sys/vtimes. h'에 선언되어 있다.


17. 6 제한된 자원 사용

당신은 프로세스의 자원 사용에 대해서 제한을 가할 수 있다. 그 프로세스가 제한을 넘어서려 시도할 때, 제한에 따라서 그 프로세스는 신호를 받거나, 아니면 시도했던 것의 실패로 인한 시스템 호출이 될 것이다. 각 프로세스는 그 부모 프로세스로부터 처음에는 제한을 상속받지만, 그후에는 그들을 변경 할 수 있다. 이 절에 있는 심볼들은 `sys/resource. h'에 정의되어 있다.

함수 : int getrlimit(int resource, struct rlimit *rlp)

자원 resource의 현재값과 최대 값을 읽고, 그들을 *rlp에 저장한다. 성공하면 반환값은 0이고 실패하면 -1이다. 발생 가능한 에러 상황은 EFAULT뿐이다.

함수 : int setrlimit(int resource, struct rlimit *rlp)

*rlp에 있는 것으로 자원 resource의 현재값과 최대 값을 설정한다. 성공하면 반환값은 0이고 실패하면 -1이다. 다음의 errno는 가능한 에러상황이다.

EPERM

당신이 허용된 최대 제한값을 변경하려 시도했지만, 당신은 그렇게 할만한 특권을 부여받지 않았다.

데이터타입 : struct rlimit

이 구조체는 제한 값들을 받기 위해서 getrlimit에서 사용되고, 제한 값들을 정하기 위해서 setrlimit에서 사용된다. 두 개의 필드를 갖는다.

rlim_cur : 질문에 대한 제한의 현재값.

rlim_max

질문에 대한 제한의 최대 허용값. 당신은 이 최댓값 보다 큰 수로 제한의 현재값을 설정할 수 없다. 오직 슈퍼 유저만이 최대 허용값을 변경할 수 있다.

getrlimit에서 그 구조체는 출력으로서, 현재값들을 받는다. setrlimit에서 그 구조체는 새로운 값들을 정하는데 사용된다. 당신이 제한을 정할 수 있는 자원의 리스트이다. 그들은 바이트로 계산된 크기이다.

RLIMIT_CPU

그 프로세스가 사용할 수 있는 cpu time의 최대량. 만일 이것보다 더 길게 실행한다면, SIGXCPU라는 신호가 발생한다. 값은 초단위로 계산된 것이다. 21. 2. 7절 [Nonstandard Signals] 참조.

RLIMIT_FSIZE

그 프로세스가 만들 수 있는 파일의 최대 크기. 이것보다 큰 파일을 만들려하면 SIGXFSZ라는 신호가 발생된다. 21. 2. 7절 [Nonstandard Siganls] 참조.

RLIMIT_DATA

프로세스가 사용할 수 있는 데이터 메모리의 최대크기. 만일 그 프로세스가 이 양을 초과하는 데이터 메모리를 할당하려 시도하면 그 할당은 실패한다.

RLIMIT_STACK

프로세스를 위한 최대 스택 크기. 만일 프로세스가 이 크기보다 크게 스택을 확장하려 시도한다면, SIGSEGV 신호가 발생된다. 21. 2. 1절 [Program Error Signals] 참조.

RLIMIT_CORE

이 프로세스가 만들 수 있는 최대 크기 코어 파일. 만일 프로세스가 종료되고 코어파일이 만들어졌고, 이 최대크기가 충분하지 않다면, 코어 파일은 잘려진다.

RLIMIT_RSS

이 프로세스가 얻을 수 있는 물리적 메모리의 최대량. 이 수치는 시스템 스케줄러와 메모리 할당자를 위한 정보가 된다; 시스템은 메모리가 남아있는 양이 있을 때 그 프로세스에게 더 많은 메모리를 줄 수도 있다.

RLIMIT_OPEN_FILES

프로세스가 개방할 수 있는 파일의 최대개수. 만일 이것보다 더 많이 개방하려 한다면, EMFILE라는 에러코드가 발생된다. 2. 2절 [Error Codes] 참조.

RLIM_NLIMITS

다른 자원 제한의 개수. 어느 유용한 자원 피연산자는 RLIM_NMLIMITS보다 적을 것임이 틀림없다.

상수 : int RLIM_INFINITY

이 상수는 setrlimit에서 제한 값으로 사용될 때 "무한대"의 값을 나타낸다.
자원 제한을 설정하는 함수는 ulimit와 vlimit가 있지만 여기서 설명되지 않았다. vlimit는`sys/vlimit. h'에 선언되어 있고 BSD로부터 왔다.


17. 7 프로세스 우선권

여러개의 프로세스들이 실행하려 시도될 때, 그들 각각이 가진 우선권이 그 프로세스가 가져갈 CPU의 분할을 결정한다. 이 절은 당신이 어떻게 프로세스의 우선권을 알아내고 설정할 수 있는지를 설명한다. 이들 함수들과 매크로 모두는 `sys/resource. h에 선언되어 있다.

유용한 우선권 값들의 범위는 운영체제에 의존되지만, 일반적으로 -20에서 20까지에서 실행된다. 낮은 우선권 값은 그 프로세스가 더 자주 실행됨을 의미한다. 이들 상수들은 우선권 값들의 범위를 설명한다.

PRIO_MIN

유용한 우선권의 가장 작은 값.

PRIO_MAX

유용한 우선권의 가장 큰 값.
** 역자주 : 원서에는 PRIO_MAX에 The smallest라고 되어 있었습니다. 하지만 저는 틀렸다는 판단을 갖고 largest라고 고쳤음을 알려 드립니다.

함수 : int getpriority(int class, int id)

프로세스들 class의 우선권을 읽는다; class와 id는 밑에 있는 것 중에 한가지로 정한다.
 
성공하면 반환값을 우선권 값이고, 실패하면 -1을 반환한다. 다음의 errno는 이 함수에서 가능한 에러상황이다.

ESRCH

class와 id의 조합이 현존하고 있는 어떤 프로세스와도 맞지가 않는다.

EINVAL

class의 값이 유용하지 않다.
반환값이 -1일 때, 그것은 실패했음을 알리거나, 또는 그것이 우선권의 값이 될 수 있다. 이럴때를 대비한 정확한 방법은 getpriority를 호출하기 전에 errno = 0으로 설정을 해놓으면, 실패를 판단하는 조건으로 errno != 0 을 사용할 수 있다.

함수 : int setpriority(int class, int id, int priority)

프로세스들 class의 우선권을 읽는다(설정한다. ); class 와 id는 밑에 것 중에 하나로 설정한다.
 
성공하면 반환값은 0이고 실패하면 -1이다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.
 
** 역자주 : 역시 이곳에서도 문맥상 Read가 맞지가 않습니다. 책에는 Read라고 나와있지만, 함수 이름이나, 다음 문장들을 참고로 보건대 저는 Set이 맞다고 생각합니다.

ESRCH : class와 id의 조합이 현존하는 어떤 프로세스와도 맞지가 않는다.

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

EPERM

당신이 다른 사용자의 프로세스의 우선권을 설정하려 시도하였고, 당신은 그것을 위한 특권을 가지지 않았다.

EACCES

당신이 어떤 프로세스의 우선권을 낮추려 시도했고, 당신은 그것을 할만한 특권이 부여되지 않았다.
인수 class와 id는 함께 당신이 우선권을 읽거나, 설정할 프로세스의 집합을 설정한다. 이들은 class에서 사용할 수 있는 값들이다.

PRIO_PROCESS

한 프로세스의 우선권을 읽거나 설정하라. 인수 id는 프로세스의 ID이다.

PRIO_PGRP

한 프로세스 그룹의 우선권을 읽거나 설정하라. 인수 id는 프로세스 그룹 ID 이다.

PRIO_USER

한 사용자의 프로세스들의 우선권을 읽거나 설정하라. 인수 id는 사용자 ID이다.
만일 인수 id가 0이면, class에 따라서 현재 프로세스, 현재 프로세스 그룹 또는 현재 사용자를 나타낸다.

함수 : int nice(int increment)

increment로 현재 프로세스의 우선권을 증가시킨다. 반환값은 의미가 없다. 다음은 nice함수와 같은 일을 하는 함수이다. 즉. . nice의 정의이다.
int nice (int increment)
{
int old = getpriority (PRIO_PROCESS, 0);
setpriority (PRIO_PROCESS, 0, old + increment);
}


목차 이전 : 16. 패턴 매칭 다음 : 18. 확장된 문자들

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

19. 지역과 세계화  (0) 2007.12.22
18. 확장된 문자  (0) 2007.12.22
17. 날짜와 시간  (0) 2007.12.22
16. 형식일치( Pattern Matching )  (0) 2007.12.22
15. 탐색과 정렬  (0) 2007.12.22
14. 저수준 연산 함수들  (0) 2007.12.22
Comment 0 Trackback 0
Top

16. 형식일치( Pattern Matching )

목차 이전 : 15. 탐색과 정렬 다음 : 17. 날짜와 시간


16 패턴 매칭 ( Pattern Matching )

GNU C 라이브러리는 두 종류의 형태(pattern)를 조화시키기 위한 도구를 제공한다: 보통의 표현들과 파일-이름 와일드카드. 라이브러리는 또한, 쉘이 하는 방법대로 변수를 확장하고 명령을 참조하고, 단어로 텍스트를 구문 분석하는 그러한 도구들을 제공한다.


16. 1 와일드카드 매칭 ( Winldcard Matching )

이 절은 특별한 문자열에 대응하여 와일드카드(wildcard) 패턴과 어떻게 매치할 것인지를 설명한다. 그 결과는 예, 또는 아니오 의 대답이다: 그 문자열이 패턴과 맞는지 틀린지. . . 여기에 설명된 모든 기호(sysbols)들은 모두 'fnmatch. h/에 선언되어 있다.

함수 : int fnmatch (const char *pattern, const char *string, int flags)

이 함수는 문자열 string 이 패턴 pattern 과 맞는지, 어떤지를 시험한다. 만일 그들이 서로 맞으면 0을 반환하고; 그렇지 않으면 영이 아닌 값 FNM_NOMATCH 를 반환한다. pattern 과 string인수들은 둘다 문자열이다. flags 인수는 세밀한 매칭(matchin)을 하기 위한 플래그 비트들의 조합니다. 정의된 플래그들은 아래를 참조하라.

GNU C 라이브러리에서, fnmatch는 "에러"를 발생시킬 수 없다. 그것은 항상 그 둘이 서로 맞는지에 대한 대답을 반환하기 때문이다. 그렇지만, fnmatch에서 파생된 다른 함수들은 때때로 "에러"를 보고할 것이다. 그들은 FNM_NOMATCH 과는 다른 0이 아닌 값을 반환함으로써 에러를 보고 할 것이다. 이들은 flags 인수에서 사용되는 유용한 플래그들이다.

FNM_FILE_NAME

파일이름과 맞추는 동안, '/' 문자를 특별히 취급하라. 만일 이 플래그가 설정되면, 패턴안의 와이드카드 구성들은 문자열에 있는 '/'과 매치할 수 없다. 그래서, '/'과 매치하기 위한 유일한 방법은 패턴안에 '/'를 명시하는 것이다.

FNM_PATHNAME

이것은 FNM_FILE_NAME 의 다른 이름으로; POSIX. 2로부터 왔다. 우리는 파일 이름으로 "경로이름"을 사용하지 않기 때문에 이 이름을 쓰는 것을 권장하지 않는다.

FNM_PERIOD

만일 문자열의 처음에 '. ' 문자가 나타나면 특별하게 취급하라. 만일 이 플래그가 설정되면, 패턴안의 와일드카드 구성은 첫 번째 문자가 '. '로 시작되는 문자열에서 '. '문자와 매치할 수 없다. 만일 당신이 FNM_PERIOD 와 FNM_FILE_NAME 을 모두 설정해 놓았다면, 문자열의 처음에 나타나는 '. ' 은 물론 '. '문자 다음에 나타나는 '/'문자도 특별한 취급을 한다. (쉘은 FN, _PERIOD 와 FNM_FILE_NAME 플래그를 파일이름과 매치하기 위해서 함께 사용한다. )

FNM_NOESCAPE

패턴안에 있는 '\'문자를 특별하게 취급하지 말아라. 보통, '\'가 바로 뒤에 따르는 문자를 인용하면, 원래 그 문자가 갖는 특별한 의미가 무효화된다. 인용이 가능했을 때, 패턴 '\?'는 패턴안의 의문부호가 보통의 문자처럼 행동하기 때문에 '?'과 매치된다. 만일 당신이 FNM_NOESCAPE을 사용하면, '\'도 보통의 문자가 된다.

FNM_LEADING_DIR

문자열에서 '/'뒤에 나타나는 문자들의 열을 무시하라; 이것은, 패턴과 매치되는 디렉토리 이름으로 시작하는 문자열인지를 시험함을 말한다. 만일 이 플래그가 설정되면, 패턴이 'foo*' 이거나 'foobar' 이면, 문자열 'foobar/frobozz' 과 매치될 것이다.

FNM_CASEFOLD

패턴에 비교되는 문자열의 대, 소문자 구분을 무시하라.


16. 2 Globbing

와일드카드의 전형적인 사용은 디렉토리 안에 있는 파일들과 맞추어서 매치되는 모든 파일들의 리스트를 만들기 위함이다. 이것은 globbing이라고 부른다.

당신은 fnmatch 를 사용해서 하나씩 디렉토리 엔트리들(entries)을 읽고, 테스트함으로써 이런 일을 할 수가 있다. 그렇지만, 속도가 느리다. (그리고 직접 서브디렉토리들(subdirectories)을 다루기 때문에 복잡하다. )

라이브러리는 편리한 와일드카드를 특별하게 사용하도록 하기 위해 glob 함수를 제공한다. glob 과 이 절에 있는 다른 심볼들은 'glob. h'에 선언되어 있다.

16. 2. 1 glob 호출하기

globbing의 결과는 파일 이름들(문자열)의 벡터(vector)이다. 이 벡터를 반환하기 위해서, glob은 구조체인 golb_t 라는 특별한 데이터타입을 사용한다. 당신이 glob에게 구조체의 주소를 주면, glob은 그 구조체의 각 필드를 채워서 당신에게 그 결과를 알린다.

데이터타입 : glob__t

이 데이터타입은 벡터를 가리키는 포인터를 저장한다. 더 자세하게 말하자면, 그것은 벡터의 주소와 그 크기를 갖고있는 레코드이다.

gl_pathc 벡터 안에 있는 요소들의 개수

gl_pathv 벡터의 주소. 이 필드는 char ** 형을 갖는다.

gl_offs gl_pathv 필드 안에 있는 명목상의 주소로부터 구한, 벡터의 첫 번째 실 요소의 offset. 다른 필드들과 달리, 이것은 glob으로부터 나온 출력이 아니라, 항상 glob에서 입력으로 사용된다. 만일 당신이 0이 아닌 offset을 사용하면, 벡터의 시작점으로부터 다른 요소들은 비어있는 채로 왼쪽에 존재한다. ( glob 함수는 널 포인터로 그들을 채운다. ) gl_offs 필드는 당신이 GLOB_DOOFFS 플래그들 사용할 때만 의미가 있다. 그렇지 않다면, offset은 이 필드 안에 무엇이 있던지 상관없이 항상 0이고, 첫 번째 실 요소는 벡터의 시작점에 있다.

함수 : int glob (const char *pattern, int flags, int (*errfunc) (const char *filename, int error-code), glob_t *vector_ptr)

glob 함수는 현재의 디렉토리에서 패턴 pattern을 사용해서 globbing을 한다. 그것은 새로이 할당된 벡터에 그 결과를 넣고, *vector'ptr에 이 벡터의 주소와 크기를 저장한다. flags인수는 비트플래그들의 조합이다; 플래그들에 대한 상세한 정보는 16. 2. 2절 [Flags for Globbing] 를 참조하라.

globbing의 결과는 파일이름들의 문자열이다. glob 함수는 각 결과를 위해서 워드(word) 단위의 문자열을 할당하고, 이들 문자열의 주소를 저장하기 위해서 char ** 형의 벡터를 할당한다. 벡터의 마지막 요소는 널 포인터이다. 이 벡터는 워드 벡터(word vector)라고 불린다. 이 벡터를 반환하기 위해서, glob는 *vector'ptr에 그 주소와 길이를 (널 포인터로 끝나는 것을 세지않은, 요소들의 개수) 저장한다. 보통, glob는 그들을 반환하기 전에 알파벳순으로 파일 이름들을 정렬한다. 당신은 만일 당신이 가능한 한 빨리 그 정보를 얻기를 원한다면 GLOB_NOSORT 플래그를 사용해서 이 기능을 이용하지 않을 수 있다.

만일 당신이 알파벳순으로 파일들을 처리한다면, 보통은 glob가 정렬하도록 하는 것이 좋고, 당신이 만든 응용프로그램을 사용하는 사용자들은 프로그램에 대한 가치를 느낄 것이다.

glob 함수가 성공하면 0을 반환하고, 그렇지 않다면 그것은 다음 에러코드 중 하나를 반환한다.

    GLOB_ABORTED

    디렉토리 개방에서 에러가 있었고, 그리고 당신이 GLOB_ERR 플래그를 사용했거나 errfunc가 0이 아닌 값을 반환했다. GLOB_ERR 플래그와 errfunc 에 대한 설명은 아래를 참조하라.

    GLOB_NOMATCH

    pattern이 현존하는 파일들과 아무 것도 맞지 않는다. 만일 당신이 GLOB_NOCHECK 플래그를 사용하면, 이 에러코드는 결코 나오지 않는다. 왜냐하면 이 플래그는 적어도 한 개의 파일이 패턴과 맞는 것이 있는 것처럼 glob이 가장하도록 하기 때문이다.

    GLOB_NOSPACE

    그 결과를 저장하기 위한 메모리 할당이 불가능하다. 에러가 발생하면, glob는 지금까지 발견한 pattern과 매치되는 것을 *vector`ptr에 정보를 저장한다.

 

16. 2. 2 Globbing 을 위한 플래그들

이 절은 glob 에게 flags 인수로 지정할 수 있는 플래그들을 설명한다. 당신이 원하는 플래그를 선택하고, 비트별 OR 연산자 | 을 사용해서 그들을 조합하라.

GLOB_APPEND

앞서서 호출한 glob이 만들어낸 워드 벡터에 새로운 워드벡터를 붙인다. 이것은 그들 사이를 공백으로 연결해서 여러 워드들을 효율적으로 확장할 수 있다. 붙이는 작업을 하려면, glob 호출동안 구조체 워드 벡터의 내용을 변화시켜서는 안 된다. 그리고 만일 당신이 glob의 처음 호출에서 GLOB_DOOFFS 를 설정하면, 당신이 그 결과들을 붙일 때도 반드시 그것을 설정해야만 한다. gl_pathv 에 저장한 포인터는 당신이 두 번째 glob을 호출한 이후에는 더 이상 유용한 정보가 아님을 기억하라. 왜냐하면, glob이 그 벡터의 위치를 바꾸기 때문이다. 그러므로 항상 매번 glob을 호출한 후에 바로 구조체 glob_t 에서 gl_pathv를 추출하라; 호출을 거쳐 그 포인터를 결코 저장하지 말아라.

GLOB_DOOFFS

워드 벡터의 시작점에 빈 슬롯을 남겨라. gl_offs는 얼마나 많은 슬롯을 남겨야 하는지를 알린다. 빈 슬롯은 널 포인터를 저장한다.

GLOB_ERR

즉시 포기하고 순서대로 읽혀져야만 하는 디렉토리를 읽는데 어떤 어려움이 있다면, 그 에러를 보고하라. 그와같은 어려움 들은 당신이 필요한 접근을 갖지 못한 디렉토리를 포함했기 때문일 것이다. 보통, glob는 그 디렉토리가 무엇이든지, 어떤 에러에도 불구하고, 계속 실행하려 시도한다. 당신은 glob를 호출할 때 에러-처리 함수 errfunc를 정함으로 해서 이것보다 더 많은 제어를 실행할 수 있다. 만일 errfunc 가 널 포인터가 아니라면, glob는 디렉토리를 읽을 수 없을 때 즉시 실행을 멈추지 않고; 대신 다음처럼 두 개의 인수를 사용해서 errfunc 함수를 호출한다:
(*errfunc) (filename, error-code)
filename인수는 glob이 개방할 수 없거나, 읽을 수 없었던 디렉토리의 이름이고, error-code는 glob에서 보고된 에러 값이다. 만일 에러처리 함수가 영이 아닌 값을 반환하면, glob는 즉시 멈춘다. 그렇지 않다면 계속 실행한다.

GLOB_MARK

만일 pattern이 디렉토리 이름과 매치되면, 그것을 반환할 때, 디렉토리의 이름에 '/'를 덧붙여라.

GLOB_NOCHECK

만일 pattern이 어떤 파일 이름과도 매치가 되지 않으면, 매치되는 파일 이름이 있는 것처럼 pattern 그 자체를 반환한다. (보통, pattern이 어느것과도 매치가 안될 때, glob는 매치되는 것이 아무 것도 없음을 반환한다. )

GLOB_NOSORT

파일 이름들을 정렬하지 말아라; 특별한 순서없이 그들을 반환하다. ( 실제로, 그 순서는 디렉토리에 있는 엔트리의 순서에 의존할 것이다. ) 정렬하지 않는 유일한 이유는 시간을 절약하기 위함이다.

GLOB_NOESCAPE

pattern들에 있는 '\' 문자를 특별하게 취급하지 말아라. 보통, '\'가 그 다음에 나타나는 문자들을 인용해서, 그 문자들이 갖는 특별한 기능을 없앤다. 인용(quoting)이 가능할 때, 패턴 '\?'은 오직 문자열 '?'로 매치되는데, 그 이유는 pattern에 있는 의문부호가 보통의 문자처럼 행동하기 때문이다.
만일 당신이 GLOB_NOESCAPE를 사용하면, '\'은 보통의 문자가 된다. glob가 반복적으로 fnmatch 함수를 호출함으로써 그 작업을 한다. fnmatch의 호출에서 FNM_NOESCAPE가 켬으로 해서 GLOB_NOESCAPE 플래그를 취급한다.


16. 3 정규식 매칭 ( Matching )

GNU C 라이브러리는 정규식을 매치하기 위한 두 개의 인터페이스를 제공한다. 하나는 표준 POSIX2 인터페이스이고, 다른 하나는 GNU 시스템이 오랫동안 가지고 있었던 것이다.

두 개의 인터페이스는 헤더파일 'regex. h'에 선언되어 있다. 만일 당신이 _POSIX_C_SOURCE 라고 정의하면, 오직 POSIX. 2의 함수들, 구조체, 그리고 상수들로만 선언되어 진다.

 

16. 3. 1 POSIX 정규식 컴파일

당신이 실제로 정규식을 매치하기 전에, 당신은 그것을 컴파일해야만 한다. 이것은 실제로 우리가 생각하고 있는, 기계 명령어로 바꾸는 그런 실제의 컴파일이 아니라 특별한 데이터 구조체를 생성하는 것을 말한다. 그러나 그것은 당신은 그 패턴을 "실행"이 가능하도록 만들려 하는 목적을 갖고 있다는 점에서 보통의 컴파일과 같다. (컴파일된 일반 표현식을 어떻게 매치하는지에 대한 정보는 16. 3. 3절 [Matching POSIX Regexps] 참조. )

컴파일된 정규식을 위한 특별한 데이터 타입은 아래와 같다.

데이터타입 : regex__t

이것은 컴파일된 정규식을 저장하는 오브젝트의 타입이다. 그것은 실제로 구조체이다. 당신은 당신의 프로그램에서 밑에 보여진 오직 하나의 필드만 가진다.
re_nsub 이 필드는 컴파일된 정규식에 존재하는 괄호로 묶여진 부정규식(subexpressions)의 개수를 저장한다.
 
그 구조체에는 여러 가지 다른 필드들이 있지만, 우리는 여기서 그들을 설명하지 않겠다. 왜냐하면 다른 필드들은 우리가 임의대로 건드릴 수 없고, 오직 라이브러리 함수에서만 그들을 사용하기 때문이다. 당신이 regex_t 오브젝트를 만든 후에, 당신은 regcomp를 호출함으로써 정규식을 컴파일할 수 있다.

함수 : int regcomp (regex_t *compiled, const char *pattern, int cflags)

regcomp 함수는 문자열과 매치시키는데 사용하는 regexec를 사용할 수 있도록 정규식을 데이터 구조체로 "컴파일"한다. 컴파일된 정규식의 형식은 매칭(matching)에 효율적이게 만들어졌다. regcomp는 *compiled에 그것을 저장한다.
 
당신은 regex_t 타입의 오브젝트를 할당한 다음 그 주소를 regcomp에게 주어라. 인수 cflags는 정규식의 구문과 의미들을 제어하는 다양한 옵션을 정할 수 있도록 허용한다. 16. 3. 2절 [Flags for POSIX Regexps] 참조. 만일 당신이 REG_NOSUB플래그를 사용한다면, regcomp는 어떻게 부정규식이 실제로 매치되는지를 기록하기 위해 필요한 정보를 컴파일된 정규식에서 생략한다. 이 경우, 당신은 regexec를 호출할 때 matchptr 과 nmatch인수로 0을 사용하는 것이 좋을 것이다. 만일 당신이 REG_NOSUB 를 사용하지 않는다면, 컴파일된 정규식은 어떻게 부정규식을 매치하는지를 기록하는 용량을 갖는다. 또한, regcomp는 얼마나 많은 부정규식 패턴을 가졌는지를 compiled->re_nsub 에 저장하여 당신에게 알린다. 당신은 그것을 부정규식 매치에 대한 정보를 저장할 곳을 할당하기 위해 얼마나 긴 배열을 할당할지를 정하기 위한 값으로 사용할 수 있다.
 
regcomp는 정규식을 컴파일하는데 성공하면 0을 반환하고; 그렇지 않으면 0이 아닌 에러코드( 밑에 설명된 )를 반환한다. 당신은 그 에러코드의 발생이유를 설명할 에러메시지를 만들기 위해서는 regerror를 사용할 수 있다; 16. 3. 6절 [Regexp Cleanup] 참조. 이것들은 regcomp가 반환할 수 있는 0이 아닌 값들이다.

REG_BADBR

정규식안에 유용하지 않은 `\{. . . \}` 구성이 있었다. 유용한 `\{. . . }\` 구성은 단일한 숫자, 또는 콤마로 분리된 오름차순으로 된 두 개의 숫자 중 하나를 포함해야만 한다.

REG_BADPAT

정규식에 구문에러가 있었다.

REG_BADRPT

`?'나 `*'와 같은 반복 연산자가 나쁜 위치에 있다(선행하는 아무런 부표현식도 없이 ).

REG_ECOLLATE

정규식이 유용하지 않은 대조(collating) 요소를 참조하였다. (문자열 대조를 위해서 현재의 지역에서 정의되지 않은 것. ) 19. 3절 [Locale Categories] 참조. )

REG_ECTYPE

정규식이 유용하지 않은 클래스 이름을 참조하였다.

REG_EESCAPE

정규식이 `\' 으로 끝났다.

REG_ESUBREG

`\digit' 구성에 유용하지 않은 숫자가 있었다.

REG_EBRACK

정규식 안에 균형이 맞지 않는 sqrare brackets([, ])가 있었다.

REG_EPAREN

연장된 정규식이 균형이 맞지 않는 괄호를 갖었거나, 기본 정규식이 균형이 맞지 않는 `\(' 와 `\)'를 가졌다.

REG_EBRACE

정규식이 균형이 맞지 않는 `\{' 와 `\}'을 가졌다.

REG_ERANGE

범위 표현식에서 끝점의 하나가 유용하지 않다.

REG_ESPACE

regcomp가 메모리를 다 써버렸다.

 

16. 3. 2 POSIX 정규식을 위한 플래그들

regcomp로 정규식을 컴파일할 때 피연산자 cflags에서 사용할 수 있는 비트플래그들에 대한 설명이다.

REG_EXTENDED

기본 정규식이 아닌, 연장된 정규식으로 패턴을 취급하라.

REG_ICASE

문자들을 매치할 때 대, 소문자 구분을 무시하라.

REG_NOSUB

match_ptr 배열에 저장한 내용들을 건드리지 말아라.

REG_NEWLINE

문자열에 있는 새줄문자를 여러 개의 라인으로 문자열을 나누는 역할을 하는 것처럼 취급한다, 그래서 `$'은 새줄문자 전에 매치할 수 있고, `^'은 새줄문자 후에 매치할 수 있다. 또한, 새줄문자와 매치하기 위해 `. '을 허용하지 않고, `[^. . . ]'을 허용하지 않는다. 그렇지 않다면 새줄문자는 다른 보통의 문자들처럼 작용한다.

 

16. 3. 3 컴파일된 POSIX 정규식을 매칭하기

일단 당신이 16. 3. 1절 [POSIX Regexp Compilation] 에서 설명된 것처럼 컴파일된 정규식을 가졌다면, 당신은 regexec를 사용해서 문자열과 그것을 매치할 수 있다. 만일 정규식이 `^' 나 `$' 와 같은 고정문자들을 표함하고 있지 않다면, 문자열 안에 어디에서든지 매치되는 것은 성공으로 센다.

함수 : int regexec(regex_t *compiled, char *string, size_t nmatch, regmatch_t matchptr [], int eflags)

이 함수는 문자열을 컴파일된 정규식*compiled와 매치하려 시도한다. regexec 는 만일 정규식이 매치되면 0을 반환하고; 그렇지 않다면 0이 아닌 값을 반환한다. 0이 아닌 값들이 무엇을 의미하는지 밑 테이블에 설명해 놓았다.
 
당신은 0이 아닌 값이 발생하는 이유를 설명할 에러 메시지를 생성하도록 regerror 함수를 사용할 수 있다; 16. 3. 6절 [Regexp Cleanup] 참조. 인수 eflags는 다양한 옵션들을 가능하도록 하는 비트플래그들의 집합인 워드이다. 만일 당신이 문자열이 정규식과 매치된 곳이 실제로 어느 부분인지 또는 그 부표현식은 무엇인지를 알기를 원한다면 인수 matchptr 과 nmatch를 사용하라.
 
그렇지 않다면 nmatch에는 0을 주고 matchptr에는 NULL을 주어라. 16. 3. 4절 [Regexp Subexpressions] 참조. 당신이 정규식을 컴파일했을 때 그 컴파일된 정규식은 실제로 그것이 현재 위치한 지역에 맞도록 정규식을 매치해야만 한다. regexec 함수는 eflags 인수에서 다음 플래그들을 받아들인다.

REG_NOTBOL

한 라인의 시작이 정해진 문자열로 시작하는지를 신경 쓰지 말아라; 즉, 어떤 텍스트의 앞에 무엇이 선행해야만 한다는 어떤 가정도 만들지 말아라.

REG_NOTEOL

한 라인의 끝이 정해진 문자열로 끝나는지를 신경 쓰지 말아라; 즉, 어떤 텍스트의 뒤에 무엇이 따라와야만 한다는 어떤 가정도 만들지 말아라.

regexec가 반환할 수 있는 0이 아닌 값들에 대한 설명.

    REG_NOMATCH

    패턴이 문자열과 매치되는 것이 없다. 이것은 사실상 에러는 아니다.

    REG_ESPACE

    regexec가 메모리를 다 써버렸다.

 

16. 3. 4 부표현식 ( Subexpressions ) 과 매치한 결과

regexec함수가 패턴의 괄호로 묶인 부문자열을 매치할 때, 그것은 매치하려는 문자열의 부분들을 기록한다. 그것은 구조체 regmatch_t 형인 요소를 가진 배열 안의 offsets 에 저장하여 그 정보를 반환한다. 그 배열(index 0)의 첫 번째 요소는 전체 정규식과 매치된 문자열의 부분을 기록한다. 배열의 다른 요소들은 단 하나의 괄호로 묶인 부표현식과 매치된 부분의 처음과 끝을 기록한다.

데이터타입 : regmatch__t

이것은 당신이 regexec 함수에서 사용하기 위한 배열 matcharray의 데이터타입이다. 그것은 다음과 같은 두 개의 필드들을 갖고 있다.
rm_so 문자열에 있는 부문자열의 시작점의 offset. 그 부분의 주소를 얻기 위하여 문자열에 이 값을 더하라.
rm_eo 문자열에 있는 부문자열의 끝점의 offset

데이터타입 : regoff__t

regoff_t는 signed integer 형의 다른 이름이다. regmatch_t의 필드들은 regoff_t의 형을 갖는다. regmatch_t 요소들은 부표현식의 위치에 대한 것이다; 첫 번째 요소( index 1)는 매치된 첫 번째 부표현식이 어디에 있는지를 기록하고, 두 번째 요소는 두 번째 부표현식을 기록하고, 등등. 부표현식의 순서는 그들이 나타난 순서이다.

당신이 regexec를 호출할 때, 얼마나 긴 matchptr 배열이 있는지, nmatch 인수를 가지고 정해야한다. 이것은 regexec함수에게, 저장하기 위한 얼마나 많은 요소들이 있는지를 알린다. 만일 실제로 정규식이 nmatch보다 더 많은 부표현식을 갖는다면, 당신은 그들의 나머지에 대한 offset정보를 제대로 얻지 못한 것이다. 그러나 이것은 그 pattern이 어떤 특별한 문자열과 매치되는지 또는 매치되지 않는지에 대한 사실을 변경하지 않는다.

만일 당신이 regexec 함수가 매치된 부표현식에 대한 어떤 정보도 반환하기를 원하지 않는다면, 당신은 nmatch에 0을 넣거나, 또는 regcomp로 pattern을 컴파일할 때 REG_NOSUB 플래그를 사용하라.

 

16. 3. 5 부표현식 매치하기의 복잡함

때때로 부표현식은 아무 문자도 없는 부문자열(substring)을 매치한다. 이것은 'f\(o*)\'와 문자열 `fum'을 매치할 때 발생한다. (그것은 실제로는 단지 `f'를 매치한다. ) 이 경우, offsets의 양쪽은 널 부문자열이 발견된 곳을 가리킨다. 이 예에서, offsets은 둘다 1이다.

때때로, 전체 정규식은 전혀 부표현식을 사용하지 않고 매치할 수 있다. 예를 들어, `ba\(na\)*'이 'ba'와 매치할 때, 괄호안에 있는 부표현식은 사용되지 않는다. 이런 일이 발생할 때, regexec는 부표현식을 위한 요소의 필드에 -1을 저장한다.

때때로 전체 정규식을 매치하기는 한 번보다 더 많은 특별한 부표현식을 매치할 수 있다. 예를 들어, `ba\(na\)*'이 문자열 `bananana'와 매치할 때, 괄호안의 부표현식은 세 번 매치된다. 이런 일이 발생할 때, regexec는 보통, 부표현식과 매치된 문자열의 마지막 부분의 offsets을 저장한다. `bananana'의 경우에 이들의 offsets은 6과 8이다.

그러나 마지막으로 매치된 것은 항상 문자열의 마지막에 있는 매치된 것이 아니다. 그것은 매치하기 위한 마지막 기회에서 우선권을 가진 것이라고 하는 것이 더 정확하다. 이것이 의미하는 것은 하나의 부표현식이 다른 것에서 발견됐을 때, 내부 부표현식을 위해 보고된 결과는 밖의 부표현식과의 마지막 매치에서 무슨 일이 일어났는지에 대해 영향을 받는다. 예를 들어, `\(ba\(na\)*s \)*' 을 문자열 `bananas bas '와 매치하는 것을 고려해보자. 첫 번째, 내부 표현식과 실제로 매치된 것은첫 번째 단어의 끝에 가까이 있다. 그러나 다시 두 번째 단어를 고려해보면, 그곳에는 매치되는 것이 아무 것도 없다. regexec는 "na" 부표현식이 사용되지 않은 것으로 보고한다.

다른예로 `\(ba\(na\)*s \|nefer\(ti\)* \)*'을 'bananas nefertiti'와 매치할 때 이 규칙이 적용되는지를 알아보자. "na" 부표현식은 첫 번째 단어에서 매치되는 것이 있지만, 두 번째 단어에서는 매치되는 것이 없다. 다시 한번, 밖의 부표현식의 두 번 반복은 첫 번째를 무시하고, 두 번째 반복에서 "na" 부표현식은 사용되지 않았다, 그래서 regexec는 "na" 부표현식이 사용되지 않았음을 보고한다.

 

16. 3. 6 POSIX Regexp 매치하기 소거

당신이 컴파일된 정규식을 다 사용했을 때, 당신은 regfree를 사용해서 그 저장공간을 해제할 수 있다.

함수: void regfree (regex_t *compiled)

regfree를 호출해서 *compiled가 가리키고 있는 모든 저장영역을 해제할 수 있다. 이것은 이 매뉴얼에서는 설명하지 않았지만, 구조체 regex_t의 다양한 내부적 필드들을 포함하고 있다. regfree는 오브젝트 *compiled 자체를 해제하지 않는다.
 
당신은 다른 정규식을 컴파일하기 위해 그 구조체를 사용하기 전에 regfree를 사용해서 구조체 regex_t 안에 있는 공간을 항상 해제해야한다. regcomp나 regexec에서 에러가 났을 때, 당신은 에러메시지를 출력하기 위해서 regerror함수를 사용할 수 있다.

함수: size_t regerror (int errcode, regex_t *compiled, char *buffer, size_t length)

이 함수는 에러코드 errcode를 위한 에러메시지 문자열을 생성하고, buffer에서 시작하는 메모리의 length 바이트 안에 그 문자열을 저장한다. 컴파일된 인수를 위해서, regcomp나 regexec에서 작업되었던 같은 컴파일된 정규식 구조체를 공급한다. 선택적으로, 당신은 compiled를 위해서 NULL을 공급할 수 있다; 당신은 여전히 의미 있는 에러메시지를 얻을 것이다, 그렇지만, 그것은 상세하지 않을 것이다.
 
만일 그 에러메시지가 length 바이트의 길이에 맞을 수 없다면(널 종료문자를 포함해서), 그러면 regerror는 그것은 자른다. regerror 이 저장한 문자열은 심지어 그것이 잘렸을 때라도 널 종료문자를 저장한다. regerror의 반환값은 전체 에러메시지를 저장하기 위해 필요한 최소 길이이다. 만일 이것이 length보다 적다면, 에러메시지는 잘리지 않고, 당신은 그것을 사용할 수 있다. 그렇지 않다면, 당신은 더큰 버퍼를 잡아서 다시 regerror를 호출해야한다. 이곳에 regerror를 사용하는 함수가 있는데, 에러메시지를 위한 버퍼를 항상 동적으로 할당한다.
char *get_regerror (int errcode, regex_t *compiled)
{
size_t length = regerror (errcode, compiled, NULL, 0);
char *buffer = xmalloc (length);
(void) regerror (errcode, compiled, buffer, length);
return buffer;
}


16. 4 쉘-스타일 단어 확장

단어 확장은 단어들로 문자열을 분리하고, 그것을 쉘이 하는 것처럼 변수, 명령, 그리고 와일드카드로 해석하는 것을 의미한다. 예를 들어, 당신이 `ls -l foo. c'라고 쓸대, 이 문자열은 세단어, `ls', '-l', `foo. c'로 분리된다. 이것은 단어 확장의 가장 기본적인 함수이다.

당신이 `ls *. c'라고 쓸대, 이것은 단어 `*. c'가 어느 수의 파일이름들과 대치될 수 있기 때문에 많은 단어들이 될 수 있다. 이것은 와일드카드 확장이 호출되는데, 그것은 또한 단어 확장의 일부분이다. 당신이 당신의 경로를 프린트하기 위해서 `echo $PATH'를 사용할 때, 이것도 또한 단어 확장의 일부분인 변수 치환이 이용된다. 프로그램들은 라이브러리 함수 wordexp를 호출하여서 쉘처럼 단어 확장을 수행할 수 있다.

 

16. 4. 1 단어 확장의 단계

단어 확장이 단어들의 열(suquence)에 적용될 때, 그것은 여기에 보여진 순서를 따라서 변환을 수행한다.

1. 틸드(~) 확장: `~foo'는 `foo'의 홈 디렉토리로 대치된다.
2. 다음, 세 개의 다른 변환들은 왼쪽에서 오른쪽으로, 동등한 결합순서로 적용된다.
  • 변수 치환: 환경변수들은 `$foo'처럼 참조를 위해서 대치된다.
  • 명령 치환: ``cat foo`' 와 그와 동등한 `$(cat foo)'와 같은 것들은 내부 명령을 통해서 출력으로 대치된다.
  • 산술적 확장 : `$(($x-1))'과 같은 것은 산술적 계산의 결과로 대치된다.
3. 필드 분리하기 : 텍스트를 단어로 분리한다.
4. 와일드카드 확장 : `*. c'와 같은 구석은 `. c'로 끝나는 파일이름들의 리스트로 대치된다. 와일드카드 확장은 동시에 전체 단어로 적용되고, 그들 단어들이 있는 0개의 파일, 또는 많은 파일들로 대치한다.
5. 인용 제거: 문자열-인용의 제거, 그들은 적당한 때에 변환을 금지함으로써, 그들의 작업이 수행된다.

이들 변환들에 대한 상세한 정보, 그리고 그들을 사용한 구성들을 어떻게 쓸것인가에 대한 것들은 BASH 매뉴얼을 참조하라.

 

16. 4. 2 wprdexp 호출하기

단어 확장을 위한 모든 함수들, 상수들, 그리고 데이터타입들은 헤더파일 'wordexp. h'에 선언되어 있다. 단어 확장은 단어들(문자열)의 벡터를 생성한다. 이 벡터를 반환하기 위해서, wordexp는 구조체인 wordexp_t라는 특별한 데이터타입을 사용한다. 당신이 그 구조체를 wordexp함수에 주면, 그것은 그 결과를 알리기 위해서 그 구조체의 필드를 채운다.

데이터타입 : wordexp_t

이 데이터타입은 워드 벡터를 가리키는 포인터를 저장한다. 좀 더 자세하게 말하면 워드 벡터의 주소와 그 크기를 기록한다.
 
we_wordc : 벡터에 있는 요소들의 개수
we_wordv : 벡터의 주소. 이 필드는 char ** 타입을 갖는다.
we_offs : we_wordv 필드에 있는 명목상의 주소로부터 구한 벡터의 첫 번째 실 인수의 offset. 다른 필드들과 달리, 이 필드는 wordexp함수에서 항상 입력으로 사용된다. 만일 당신이 0이 아닌 offset을 사용하면, 벡터의 시작점에서 많은 요소들은 비어있는 왼쪽에 있다. (wordexp함수는 널 포인터로 그들을 채운다. ) we_offs 필드는 당신이 WRDE_DOOFFS 플래그를 사용했을 때만 유용하다. 그렇지 않다면, offset은 이 필드 안에 무엇이 있는지 상관없이 항상 0이고, 첫 번째 실인수는 벡터의 시작점에 있다.

함수: int wordexp (const char *words, wordexp_t *word-vector-ptr, int flags)

단어들로 이루어진 문자열에서 단어 확장을 수행하고, 그 결과를 새로이 할당된 벡터에 넣고, 그리고 이 벡터의 크기와 주소를 *word-vector-ptr에 저장한다. 인수 flags는 비트플래그들의 조합이다; 플래그들에 대한 상세한 정보는 16. 4. 3절 [Flags for Wordexp] 를 참조하라.
 
당신은, 그들이 인용되지 않았다면 문자열 안에 `|&; <>' 문자들을 사용하지 못한다; 새줄도 마찬가지. 만일 당신이 인용 없이 이들 문자들을 사용한다면, 당신은 WRDE_BADCHAR 에러코드를 얻을 것이다. 그들이 인용되지 않았거나 또는 단어 확장 구성의 부분이 아니라면 괄호나, 쌍으로 이루어진 것들을 사용하지 말아라. 만일 당신이 인용 문자들 `'"`' 을 사용한다면 그들은 균형이 맞는 한 쌍이 되어야 한다. 단어 확장의 결과는 단어들의 열이다.
 
wordexp함수는 결과로 나온 단어를 위한 문자열을 할당하고, 이들 문자열의 주소를 저장하기 위해서 char ** 타입의 벡터를 할당한다. 벡터의 마지막 요소는 널 포인터이다. 이 벡터는 워드벡터라 불린다. 이 벡터를 반환하기 위해서, wordexp는 그 주소와, 그 길이(종료 널 포인터를 셈하지 않은 요소들의 개수)를 *word-vector-ptr에 저장한다.
 
만일 wordexp가 성공하면 0을 반환하고, 그렇지 않으면 다음 에러코드 중 하나를 반환한다.

WRDE_BADCHAR

입력 문자열 단어들이 인용이 없는 `|'와 같이 유용하지 않은 문자를 포함하고 있다.

WRDE_BADVAL

입력 문자열이 정의되지 않은 쉘 변수를 참조하였고, 당신이 그와같은 참조를 금하기 위해서 WRDE_UNDEF 플래그를 사용하였다.

WRDE_CMDSUB

입력 문자열이 명령 치환을 사용했는데, 당신이 명령 치환을 금하기 위해서 WRDE_NOCMD 플래그를 사용하였다.

WRDE_NOSPACE

그 결과를 저장하기위한 메모리를 할당하기가 불가능하다. 이 경우, wordexp는 할당할 수 있는 만큼 그 결과의 일부를 저장할 수 있다.

WRDE_SYNTAX

입력 문자열에 구문에러가 있었다. 예를 들어 매치되지 않는 인용 문자가 구문에러이다.

함수: void wordfree (wordexp_t *word-vector-ptr)

*word-vector-ptr이 가리키고 있는 문자열과 벡터를 위한 저장공간이 해제된다. 이것은 *word-vector-ptr 자체를 해제하지 않고, 단지 그것이 가리키고 있는 데이터만을 해제한다.

 

16. 4. 3 단어 확장을 위한 플래그들

이 절은 wordexp에서 사용되는 flags인수로 지정할 수 있는 플래그들을 설명한다. 당신이 원하는 플래그들을 선택하고, 그들을 연산자 |을 사용해서 조합하라.

WRDE_APPEND

바로 전에 호출한 wordexp가 생성한 단어의 벡터에 이번 확장으로부터 생성된 단어들을 붙여라. 이것은 그들 사이가 공백으로 분리되어져서, 여러 단어들을 효율적으로 확장할 수 있는 방법이다. 덧붙이기 위한 일련의 작업에서, 당신은 wordexp의 호출사이에 워드 벡터 구조체의 내용을 갱신하지 말아야만 한다. 그리고, 만일 당신이 wordexp의 첫 번째 호출에 WRDE_DOOFFS를 설정하면 당신은 그 결과들을 붙일 때에도 또한 그것을 설정해야만 한다.

WRDE_DOOFFS

단어들의 벡터의 시작점에 빈 슬롯들을 남겨라. we_offs 필드는 얼마나 많은 슬롯들을 남겨야하는지를 알린다. 빈 슬롯들은 널 포인터를 포함한다.

WRDE_NOCMD

명령 치환을 하지 말아라. 만일 입력이 명령 치환을 요청하면, 에러를 출력한다.

WRDE_REUSE

앞서서 호출한 wordexp에서 만들어진 워드 벡터를 재사용 하라. 새로운 벡터를 할당하는 대신에 wordexp는 이미 존재하고 있는( 필요한 만큼 그것을 크게 만들어서) 벡터를 사용할 것이다. 그 벡터를 이동할지도 모른다는 것을 기억하라, 그래서 오래된 포인터를 저장하고, wordexp를 호출한 후에 그것을 다시 사용하는 것은 안정적이지 않다. 당신은 매번 호출마다 새로운 we_pathv를 추출해야만 한다.

WRDE_SHOWERR

명령 치환에 의해 만들어진 명령으로 실행된 결과로 나온 프린트된 에러메시지를 보여라. 더 정확히, 현재 프로세스의 표준 에러 출력 스트림을 계승하도록 이들 명령들에게 허용한다. 디폴트로, wordexp는 모든 출력을 버린 표준 에러 스트림을 이들 명령들에게 준다.

WRDE_UNDEF

만일 입력이 정의되지 않은 쉘 변수를 참조한다면 에러가 출력된다.

16. 4. 4 wordexp Example

wordexp를 사용해서 여러 문자열을 확장하고, 그 결과들을 쉘 명령을 실행하기 위해 사용하는 예제가 있다. 확장들을 분리하기 위해 사용한 WRDE_APPEND와 wordexp에 의해 할당된 공간을 해제하기 위한 wordfree의 사용도 보여주고 있다.

int
expand_and_execute (const char *program, const char *options)
{
wordexp_t result;
pid_t pid
int status, i;
 
/* 그 프로그램을 실행하기 위해서 문자열을 확장하라 */
switch (wordexp (program, &result, 0))
{
case 0: /* Successful. */
break;
case WRDE_NOSPACE:
/* 만일 그 에러가 WRDE_NOSPACE라면 아마도 결과의 일부분이 할당되어진다. */
wordfree (&result);
default:
return -1;
/* 어떤 다른 에러 */
}
 
/* 인수를 위해 정해진 문자열을 확장하라. */
for (i = 0; args[i]; i++)
{
if (wordexp (options, &result, WRDE_APPEND))
{
wordfree (&result);
return -1;
}
}
 
switch( pid = fork()) {
case -1 :
status = -1;
break;
case 0 : /* 이것은 자식 프로세스이다. 그 명령을 실행하라. */
execv (result. we_wordv[0], result. we_wordv);
exit (EXIT_FAILURE);
defautl : /* 이것은 부모 프로세스이다. 자식이 완수하도록 기다려라 */
if (waitpid (pid, &status, 0) != pid)
status = -1;
}
wordfree (&result);
return status;
}

실제로, wordexp는 서브쉘(subshell)로 실행되기 때문에, 문자열에서 그들 사이를 공백으로 분리하고 쉘 명령 `sh -c'를 사용해서 실행하는 것보다는 빠르다.


목차 이전 : 15. 탐색과 정렬 다음 : 17. 날짜와 시간

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

18. 확장된 문자  (0) 2007.12.22
17. 날짜와 시간  (0) 2007.12.22
16. 형식일치( Pattern Matching )  (0) 2007.12.22
15. 탐색과 정렬  (0) 2007.12.22
14. 저수준 연산 함수들  (0) 2007.12.22
13. 수학 함수  (0) 2007.12.22
Comment 0 Trackback 0
Top

15. 탐색과 정렬

목차 이전 : 14. 저수준 연산함수들 다음 : 16. 패턴 매칭


15 탐색과 정렬

이 장은 배열을 탐색하고 정렬하기 위한 함수들을 설명하고 있다. 당신은 배열에 있는 objects의 크기와 요소의 전체개수와 함께, 인수로써 적당한 비교 함수를 줄 수 있다.


15. 1 비교 함수 정의하기

배열을 정렬하는 라이브러리 함수를 사용하기 위해서, 당신은 어떻게 그 배열의 요소들을 비교할 것인지 알려줘야만 한다. 이것을 하기 위해, 당신은 배열의 두 개의 요소를 비교하기 위한 비교함수를 공급한다. 그 라이브러리는 비교하기 위한 두 개의 배열 요소들을 가리키는 포인터가 인수로써 주어지면, 이 함수를 호출할 것이다. 당신의 비교함수는 strcmp( 5. 5절 [String/Array Comparison] 참조)가 하는 방법으로 값을 반환하는데, 그 방법이란 첫 번째 인수가 두 번째 인수보다 작으면 음수를 반환하고, 만일 같으면 0을 반환하고, 첫 번째가 크면 양수를 반환한다. 이곳에서는 double형의 숫자들로 이루어진 배열을 가지고 작업하는 비교함수의 예가 있다.

int
compare_doubles (const double *a, const double *b)
{
return (int) (*a - *b);
}

헤더파일 'stdlib. h'는 비교함수들의 데이터 타입을 위한 이름을 정의하고 있다. 이 타입은 GNU 확장이다.

int comparison_fn_t (const void *, const void *);


15. 2 배열 탐색 함수

정렬된 배열에서 키(key)값과 같은 요소를 탐색하기 위해서는, bsearch함수를 사용하라. 이 함수를 위한 프로토타입은 헤더파일 'stdlib. h'에 있다.

함수 : void *bsearch (const void *key, const void *array, size_t count, size_t size, comparison_fn_t compare)

bsearch 함수는 key 와 동등한 오브젝트를 정렬된 배열 array에서 찾는다. 그 배열은 size 바이트의 크기를 가진, count 개의 요소를 갖는다. compare 함수는 비교를 수행하는데 사용된다. 이 함수는 두 개의 포인터 인수로 호출되고 첫 번째 인수가 두 번째 인수보다 적거나, 같거나, 또는 큼에 해당하는 정수를 반환한다. 배열의 요소들은 이 비교함수에 의하여 오름차순으로 미리 정렬되어져야만 한다.
 
반환값은 key값과 같은 배열의 요소를 가리키는 포인터이거나, 또는 같은 요소를 발견하지 못하면 널 포인터를 반환한다. 만일 그 배열이 key값과 같은 것을 한 개 이상 가지고 있다면, 반환된 것은 정해지지 않았다. 이 함수는 바이너리 탐색 알고리즘을 사용해서 동작한다는 것에 기인하여 bsearch라는 이름이 유래됐다.


15. 3 배열 정렬 함수

비교 함수를 사용하여 배열을 정렬하기 위해서는, qsort 함수를 사용하라. 이 함수를 위한 프로토타입은 'stdlib. h'에 있다.

함수 : void qsort (void *array, size_t count, size_t size, comparison_fn_t compare)

qsort 함수는 배열 array를 정렬한다. 그 배열은 size의 크기를 가진 count개의 요소를 갖는다. compare 함수는 배열 요소들의 비교를 수행하는데 사용된다. 이 함수는 두 개의 포인터 인수로 호출되고 첫 번째 인수가 두 번째 인수와 비교해서 큰지, 같은지, 적은지를 알 수 있는 값을 반환한다.
주의: 만일 두 개의 오브젝트가 같다면, 정렬된 후의 그들의 순서는 예측할 수 없다. 그것은 이 정렬이 안정적이지 못하다는 것을 말하고 있다. 배열의 단지 한 부분만을 가지고 비교를 할 때 두 개의 요소가 같다고 나오는 경우, 그들은 단지 key 값만 같은 뿐이지 다른 점에 있어서는 차이가 있을 것이다.

만일 당신이 안정적인 정렬을 원한다면, 당신은 두 개의 요소사이에 부족한 다른 측면의 차이나, 그들의 주소로 그들을 비교하는 것과 같은 비교 함수를 써서 이 결과를 얻을 수 있다.

이곳은 위에 정의된 비교 함수를 사용해서, 숫자 순서로 double형의 배열을 정렬하는 예를 보여주고 있다. ( 15. 1 [Comparison Functions] 참조. )

{
double *array;
int size;
. . .
qsort (array, size, sizeof (double), compare_doubles);
}

qsort 함수는 "퀵 소트(quick sort)" 알고리즘을 사용해서 동작한다는 사실에 기인하여 qsort라는 이름이 유래되었다.


15. 4 탐색과 정렬의 예

이곳에서는 구조체의 배열에 qsort 와 bsearch를 사용하는 예를 보여주고 있다. 배열의 오브젝트들은 strcmp 함수를 사용해서 그들의 이름을 비교함으로써 정렬되어졌다.

그러면, 우리는 그들의 이름에 기초한 개개의 오브젝트들을 살펴볼 수 있다.

#include <stdlib. h>
#include <stdio. h>
#include <string. h>
 
/* 정렬할 critter들의 배열을 정의하라. */
struct critter
{
const char *name;
const char *species;
};
 
struct critter muppets[] =
{
{"Kermit", "frog"},
{"Piggy", "pig"},
{"Gonzo", "whatever"},
{"Fozzie", "bear"},
{"Sam", "eagle"},
{"Robin", "frog"},
{"Animal", "animal"},
{"Camilla", "chicken"},
{"Sweetums", "monster"},
{"Dr. Strangepork", "pig"},
{"Link Hogthrob", "pig"},
{"Zoot", "human"},
{"Dr. Bunsen Honeydew", "human"},
{"Beaker", "human"},
{"Swedish Chef", "human"}
};
 
int count = sizeof (muppets) / sizeof (struct critter);
/* 이것은 정렬과 탐색을 위해 사용하는 비교함수이다. */
int
critter_cmp (const struct critter *c1, const struct critter *c2)
{
return strcmp (c1->name, c2->name);
}
 
/* critter에 대한 정보를 프린트하라. */
void
print_critter (const struct critter *c)
{
printf ("%s, the %s\n", c->name, c->species);
}
 
/* 정렬된 배열을 살펴보라 */
void
find_critter (const char *name)
{
struct critter target, *result;
target. name = name;
result = bsearch(&target, muppets, count, sizeof (struct critter), critter_cmp);
if (result)
print_critter (result);
else
printf ("Couldn't find %s. \n", name);
}
 
/* Main 함수 */
int
main (void)
{
int i;
 
for (i = 0; i < count; i++)
print_critter (&muppets[i]);
printf ("\n");
 
qsort (muppets, count, sizeof (struct critter), critter_cmp);
 
for (i = 0; i < count; i++)
print_critter (&muppets[i]);
printf ("\n");
 
find_critter ("Kermit");
find_critter ("Gonzo");
find_critter ("Janice");
 
return 0;
}
이 프로그램의 출력은 다음과 같다.
 
Kermit, the frog
Piggy, the pig
Gonzo, the whatever
Fozzie, the bear
Sam, the eagle
Robin, the frog
Animal, the animal
Camilla, the chicken
Sweetums, the monster
Dr. Strangepork, the pig
Link Hogthrob, the pig
Zoot, the human
Dr. Bunsen Honeydew, the human
Beaker, the human
Swedish Chef, the human
 
Animal, the animal
Beaker, the human
Camilla, the chicken
Dr. Bunsen Honeydew, the human
Dr. Strangepork, the pig
Fozzie, the bear
Gonzo, the whatever
Kermit, the frog
Link Hogthrob, the pig
Piggy, the pig
Robin, the frog
Sam, the eagle
Swedish Chef, the human
Sweetums, the monster
Zoot, the human
 
Kermit, the frog
Gonzo, the whatever
Couldn't find Janice.


목차 이전 : 14. 저수준 연산함수들 다음 : 16. 패턴 매칭

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

17. 날짜와 시간  (0) 2007.12.22
16. 형식일치( Pattern Matching )  (0) 2007.12.22
15. 탐색과 정렬  (0) 2007.12.22
14. 저수준 연산 함수들  (0) 2007.12.22
13. 수학 함수  (0) 2007.12.22
12. Low-Level Terminal Interface  (0) 2007.12.22
Comment 0 Trackback 0
Top

14. 저수준 연산 함수들

목차 이전 : 13. 수학함수 다음 : 15. 탐색과 정렬


14 저수준 연산 함수들


14. 1 "숫자가 아닌" 값들

대부분의 현대적 컴퓨터들에서 사용되는 IEEE 플로팅 포인트 형식은 "숫자가 아닌" 값들도 지원한다. 이들 값들은 NaNs 라고 불린다. "숫자가 아닌" 값들은 0을 0으로 나누거나 또는 무한대를 무한대로 나누는 것과 같이, 아무런 의미 없는 숫자 결과를 갖는 어떤 명령들로부터의 결과이다.

NaNs의 주목할 만한 특성중의 하나가 그들은 그들 자신과 동등하지 않다는 것이다. 그래서, x == x 식에서 만일 x가 NaN 이라면 그 결과는 0이 될 수 있다. 당신은 어떤 값이 NaN 인지 아닌지를 테스트하기 위해서 이것을 사용할 수 있다: 만일 그것이 그 자신과 같지 않다면, 그것은 NaN이다. 그러나 NaN 인지를 테스트하기 위한 권장할 만한 방법으로는 isnan 함수가 있다. ( 14. 2절 [Predicates on Floats] 참조. )

NaN을 한 인수로 사용한 거의 모든 연산 명령들에서는 역시 NaN이 반환된다.

매크로 : double NAN

"숫자가 아닌" 값들은 표현하는데 사용. 이 매크로는 GNU 확장으로, IEEE 플로팅 포인트를 지원하는 모든 기계 상에서, 그들이 말하는, 즉, "숫자가 아닌" 값들은 지원하는 기계 상에서만 유용하다. 당신은 그 기계가 NaNs 를 지원하는지의 여부를 알아보기 위해서 '#ifdef NAN'을 사용할 수 있다. (물론, 당신은 _GNU_SOURCE로 정의해서, GNU 확장으로 정리를 해야 하고, 또한 'math. h'를 포함해야만한다. )


14. 2 플로트의 술어 ( Predrcates )

이 절은 doubles 의 잡다한 테스트 함수들을 설명한다. 이 함수들을 위한 프로토타입은 'math. h'에 있다. 그것들은 BSD 함수로 당신이 _BSD_SOURCE 또는 _GNU_SOURCE라고 정의했을 때 유용하다.

함수 : int isinf (double x)

이 함수는 x 가 무한대의 음수라면 -1을 반환하고, x 가 무한대의 양수이면 1을 반환하고, 그렇지 않으면 0을 반환한다.

함수 : int isnan (double x)

이 함수는 만일 x가 "숫자가 아닌" 것이라면 0이 아닌 값을 반환하고, 그렇지 않으면 0을 반환한다. ( 당신은 같은 결과를 얻기 위해서 x != x 도 사용할 수 있다. )

함수 : int finite (double x)

이 함수는 만일 x 가 한정된 값이거나, "숫자가 아닌" 값이라면 0이 아닌 값을 반환하고, 그렇지 않다면 0을 반환한다.

함수 : double infnan (int error)

이 함수는 BSD 와의 호환성을 위해 제공되었다. 다른 수학적 함수들이 에러가 무엇 때문에 발생했는지를 알기 위해서 infan을 사용한다. 인수로는 EDOM 이나 ERANGE 같은 에러 코드를 사용하고; 이때 infan은 이 에러에 적당한 값을 반환한다.
-ERANGE 이것 또한 인수로써 받아들여지고, -HUGE_VAL에 해당한다. BSD라이브러리로는, 어떤 기계 상에서, infnan 함수는 모든 경우에 심각한 신호를 발생시킨다. GNU 라이브러리는 ANSI C 규격에 맞지 않기 때문에 이러한 일을 하지 않는다.
 
이식성 노트 : 이 절에서 설명된 함수들은 BSD 확장이다.


14. 3 절대값

이들 함수들은 한 숫자의 절대값( 크기가 아닌 )을 얻기 위해서 제공된다. 실수 x 의 절대값은 x 가 양의 수라면 x 이고, x 가 음의 수라면 -x 가 된다. 복소수 z 에서, 그 수의 실수부 x 와 허수부 y 가 있다면, 그때 그 수의 절대값은 sqrt(x*x + y*y) 이다.

abs 와 labs 는 'stdlib. h'의 프로토타입을 갖고 fabs 와 cabs는 'math. h'에 선언되어 있다.

함수 : int abs (int number)

이 함수는 수의 절대값을 반환한다. 대부분의 컴퓨터는 2의 보수(two's complement) 정수 표현을 사용한다. 하지만 2의 보수는 INT_MIN ( 가능한 가장 작은 int )의 절대값은 표현 할 수 없기 때문에; 그래서, abs (INT_MIN)은 정의되지 않았다.

함수 : long int labs (long int number)

이 함수는 abs가 int 형을 사용하는 대신, 인수와 결과 값에 long int 형을 사용한다는 점을 제외하고는 abs와 유사하다.

함수 : double fabs (double number)

이 함수는 플로팅-포인트 수의 절대값을 반환한다.

함수 : double cabs (struct { double real, imag; } z)

cabs 함수는 실수부로 z. read 과 허수부로 z. imag를 갖고 있는, 복소수 z 의 절대값을 반환한다. ( 13. 4절 [Exponents and Logarithm] 에서 hypot 함수를 참조하라. )
그 값은 : sqrt (z. real*z. real + z. imag*z. imag)


14. 4 표준화 함수들

이 절에서 설명된 함수들은, 내부적으로 2진 기수(binary radix)를 사용하여 표현되는, 플로팅 포인트에서 일어나는 저-수준 동작들을 효율적으로 수행하기 위한 방법을 제공한다; A. 5. 3. 1절 [Floating Point Comcepts] 참조. 이들 함수들은 만일 어떤 표현이 2진수를 사용하지 않을 때라도 같은 동작을 하도록 할 필요가 있지만, 물론 그들은 그 경우에 특별히 효율적이게 보이지는 않는다. 이들 함수들 모두는 'math. h'에 선언되어 있다.

함수 : double frexp (double value, int *exponent)

frexp 함수는 숫자를 가수부과 지수부로 분리하는데 사용된다. 만일 그 인수가 0 이 아니면, 반환값은 가수부의 값이 반환되고, 그것은 항상 1/2 (포함)에서 1 (제외)의 범위안에 있다. 지수부는 *exponent 에 저장된다. 반환값에 이 2 의 지수승을 곱하면 하면 원래의 숫자값과 같다.
예를 들어, frexp (12. 8, &exponent) 는 0. 8 을 반환하고 exponent 에 4를 저장한다. 만일 값이 0이면, 반환값은 0이고 *exponent에 0이 저장되어 진다.

함수 : double ldexp (double value, int exponent)

이 함수는 2의 exponent승의 값에다, 플로팅 포인트값인 value를 곱한 결과를 반환한다. ( 그것은 frexp에 의해 떨어지게 되었던 플로팅-포인트의 값을 붙이는데 사용될 수 있다. )
예를 들어, ldexp (0. 8, 4) 는 12. 8 을 반환한다.

다음 함수들은 BSD를 위한 함수들로 ldexp 와 frexp 와 동등하다.

함수 : double scalb (double value, int exponent)

scalb 함수는 BSD용 ldexp이다.

함수 : double logb (double x)

이 BSD 함수는 2를 밑으로 하는 로그 x 값에서 정수 부분을 double형으로 반환한다. 이것은 x 에 포함된 2의 가장 높은 정수 승이다. x의 부호는 무시된다. 예를 들어, logb (3. 5) 는 1. 0 이고 logb(4. 0)은 2. 0 이다.
역자주 : 즉 3. 5는 2 + 1. 5 가 되므로. . . 2의 가장 높은 정수승이라면 1. 0이 되는 것이고, 4. 0 은 2의 2승이므로 2. 0이 된답니다.
그 결과 값은 2의 지수로 한 값이 x 로 나뉘어질 때, 그것은 1 (포함)과 2 (제외) 사이의 값이 주어진다. 만일 x 가 0이면, 그 값은 음의 무한대이고, (만일 그 기계가 그와같은 값을 지원한다면 ), 아니면, 매우 작은 값이 된다. 만일 x 가 무한대이면, 그 값도 무한대이다. logb에 반환된 값은 frexp가 *exponent에 저장한 값보다 하나가 적다.

함수 : double copysign (double value, double sign)

copysign 함수는 value의 절대값을 반환하고, sign은 그 수의 부호와 일치시킨다. 이것은 BSD 함수이다.


14. 5 라운딩과 나머지 함수들

역자주: Rounding(라운딩): 라운딩이란 원래 수학에서 수치를 표현하기 위해서 유효자리 숫자의 크기를 유지하는 방법인데. . 이곳에서는 플로팅-포인트를 정수화 하는 여러 가지 방법들을 통틀어 라운딩이라고 표현했습니다.

이곳에서 설명된 함수들은 플로팅 포인트의 나누기에서, 라운딩, 절단, 그리고 나머지와 같은 동작을 수행하는 함수들이다. 이들 함수중 어떤 것은 플로팅 포인트 숫자들은 정수 값으로 변경한다. 그들은 모두 'math. h'에 선언되어 있다.

당신은 또한 플로팅-포인트 숫자를 int 로 간단히 캐스트(casting) 함으로써 정수로 변경할 수 있다. 이것은 소수부를 버린다. 하지만, 이것은 그 결과값이 실제로 int 로서 표현가능할 때 작업하고 아주 매우 큰 수의 경우에는 불가능하다. 이 절에 있는 함수들은 이러한 문제를 유도하는 대신에 double형으로 반환한다.

함수 : double ceil (double x)

ceil 함수는 근접한 정수로 x를 올림을 하고 그 값을 double형으로 반환한다. 그래서, ceil (1. 5)는 2. 0이다.

함수 : double floor (double x)

ceil 함수는 근접한 정수로 x를 내림을 해서, 그 값을 double형으로 반환한다. 그래서, floor (1. 5)는 1. 0 이고, floor(-1. 5)는 -2. 0이다.

함수 : double rint (double x)

이 함수는 현재의 라운딩의 모드에 따라서 x를 정수 값으로 만든다. A. 5. 3. 2절 [Floating Point Parameters] 에서 다양한 라운딩모드들에 대한 정보를 참조하라. 디폴트 라운딩 모드는 가장 근접한 수로 만드는 것이다. 어떤 기계에서는 다른 모드를 지원하지만, 가장 근접한수로의 라운딩(round-to-nearest)은 당신이 명시적으로 다른 것을 선택할지라도 항상 사용된다.

함수 : double modf (double value, double *integer_part)

이 함수는 인수 value를 정수부분과 소수부분( -1과 1을 제외한 그 사이의 값)으로 분해한다. 그들의 합은 value와 같다. 그 각 부분들은 value와 같은 부호를 갖기 때문에, 정수부분의 라운딩은 0을 올림과 같다. modf는 *integer`part에 정수부분을 저장하고, 소수부분을 반환한다. 예를 들어, modf (2. 5, &intpart) 는 0. 5를 반환하고, intpart에 2. 0을 저장한다.

함수 : double fmod (double numerator, double denominator)

이 함수는 denominator로 numerator를 나눈 것의 나머지 값을 계산한다. 특별히, n이 denominator 로 numerator를 나눈 몫일 때, 그 몫을 정수로 소수점 뒤를 버리고, 그 반환값은 numerator-n*denominator 이다. 그래서, fmod(6. 5, 2. 3)의 반환값은 6. 5에서 4. 6을 뺀, 1. 9가 된다.
역자주: 즉. . . 6. 5-(6. 5/2. 3)*2. 3 = 6. 5-2*2. 3 = 6. 5-4. 6이 된다 이거죠. . 왜 이렇게 어렵게 할까~
그 결과는 numerator와 같은 부호를 갖고 denominator의 magnitude 보다 적은 magnitude 를 갖는다. 만일 denominator가 0이면, fmod는 실패하고 errno를 EDOM으로 설정한다.

함수 : double drem (double numerator, double denominator)

drem 함수는 소수점 이하의 수를 버리는 대신 가장 가까운 정수로 몫 n을 라운딩을 한다는 것을 제외하고는 fmod와 같다. 예를 들어, drem ( 6. 5, 2. 3) 은 6. 5-6. 9 이므로 -0. 4 를 반환한다. 결과의 절대값은 denominator의 절대값의 반과 같거나 더 적다. fmod (numerator, denominator) 과 drem (numerator, denominator) 와의 차이가 denominator, minus denominator, 또는 0 이거나 항상 존재한다. 만일 denominator 이 0이면, drem 은 실패하고, errno를 EDOM으로 설정한다.


14. 6 정수 나누기

이 절은 정수 나눗셈을 수행하기 위한 함수들을 설명한다. GNU C 라이브러리에 있는 함수들에서 '/' 연산자는 항상 소수점 이하를 버리는 방식으로 버림(round)을 행하지만, 다른 C 에서 '/' 는 음의 인수에는 다르게 버림(round)를 행할 것이다. div 와 ldiv 는 어떻게 몫에 버림(round)을 할 것인지를 정하기 때문에 유용하다: 소수점 이하를 버린다. 나머지는 numeraotr 과 같은 부호를 갖는다.

이들 함수들은 r. quot*denominator + r. rem 은 numerator 과 같도록 반환값 r 을 정한다. 이들을 사용하기 위해서, 당신은 당신의 프로그램에 헤더파일 'stdlib. h'를 포함시켜야 한다.

데이터 타입 : div__t

이것은 div 함수에 의해 반환된 결과를 저장하기 위해 사용되는 구조체형이다. 이것은 다음과 같은 멤버들을 갖는다.
int quot 나눗셈의 몫
int rem 나눗셈의 나머지

함수 : div_t div (int numerator, int denominator)

div함수는 numerator 을 denominator 로 나눈 것에서 몫과 나머지를 구하고, 그 결과를 구조체 div_t에 저장하여 반환한다. 만일 그 결과가 표현될 수 없다면( 영으로 나누는 것처럼), 그 결과는 정의되지 않았다.
유용한 것은 아니지만, 하나의 예를 들면. . .
div_t result;
result = div (20, -6);
이것의 결과로 나온 result. quot 는 -3 이고 result. rem 은 2이다.

데이터 타입 : ldiv__t

이것은 ldiv 함수에 의해 반환된 결과를 저장하기 위해 사용되는 구조체이다. 이것은 다음과 같은 멤버들을 갖는다.
long int quot 나눗셈의 몫
long int rem 나눗셈의 나머지
( 이것은 구조체 요소들이 int 형이 아니라 long int 라는 점을 제외하면, div_t 와 동일하다. )

함수 : ldiv_t ldiv (long int numerator, long int denominator)

ldiv 함수는 인수가 long int 형이고, 그 결과가 ldiv 형의 구조체에 반환된다는 점을 제외하고는, div 함수와 유사하다.


14. 7 숫자의 파싱 ( 구문해석 : parsing )

이 절은 문자열로부터 정수와 플로팅 포인트 숫자를 "읽기" 위한 함수들을 설명한다. 그것은 sscanf 또는 그 함수와 연관된 어떤 것보다, 어떤 경우에는 사용하기 더 편리할 것이다. 7. 11절 [Formatted Input] 참조. 당신이 직접 문자열에서 토큰을 분리해 내는 것이 더 안정된 프로그램을 만드는 경우도 있겠지만, 그러면 그때는 하나씩 그 토큰을 숫자로 변경해야만 한다.

14. 7. 1 정수의 파싱

이들 함수들은 'stdlib. h'에 선언되어 있다.

함수 : long int strtol (const char *string, char **tailptr, int base)

strtol ("스트링에서 long 으로 ") 함수는 문자열의 처음부분을 long int형으로 반환된 부호화된 정수로 바꾼다. 이 함수는 다음과 같이 문자열을 분해하려 시도한다.
공백문자(whitespace characters)의 열. 공백문자는 isspace 함수로 알아낼 수 있다( 4. 1절 [Classification of Characters] 참조. ). 이들 공백문자는 버려진다.
임의의 플러스 또는 마이너스 부호('+' 또는 '-').
 
기수가 정해진, 아무런 공백이 없는 숫자들의 열. 만일 기수가 0이면, '0'( 8진수로 정하기 ) 또는 '0x' 나 '0X'( 16진수 ) 로 시작하는 숫자열이 아니한, 십진수로 가정한다. 즉, 같은 구문을 C에서 정수 상수를 위해 사용한다. 그렇지 않다면 기수는 반드시 2와 35 사이여야만 한다. 만일 기수가 16이면, 그 숫자는 '0x' 나 '0X' 로 시작될 것이다.
 
문자열에서 남은 나머지 문자들. 만일 tailptr이 널 포인터가 아니라면, strcol은 *tailptr에 tail을 가리키는 포인터를 저장한다.
만일 그 문자열이 오직 공백으로만 되어 있거나, 정해진 기수의 정수가 가져야하는 처음의 부문자열을 포함하지 않는다면, 아무런 변환이 수행되지 않는다. 이 경우, strcol은 0을 반환하고, *tailptr에 저장된 값은 스트링의 값이다.
 
표준 "C" 지역이 아닌 다른 지역에서, 이 함수는 그 지역의 구문에 의존되는 부가적인 동작을 인식할 것이다. 만일 그 문자열이 정수에 맞는 구문을 갖고 있지만 오버플로우 때문에 표현할 수 없다면, strtol은 그 값의 부호에 알맞은 LONG_MAX 또는 LONG_MIN을 반환한다(A. 5. 2절 [Range of Type] 참조. ) 또한 오버플로우가 났음을 알리기 위해서 errno를 ERANGE로 설정한다. 이것에 대한 예는 이 절의 마지막에 있다.

함수 : unsigned long int strtoul (const char *string, char **tailptr, int base)

strtoul (" 문자열을 unsigned long 으로 ") 함수는 unsigned long int 형으로 그 값을 반환한다는 것을 제외하고는 strtol함수와 같다. 오버플로우가 발생할 경우에 반환되는 값은 ULONG_MAX 이다(A. 5. 2 절 [Range of Type] 참조. )

함수 : long int atol (const char *string)

이 함수는 오버플로우 에러를 검출할 필요가 없다는 점을 제외하고, base 인수로 10을 사용한 strtol 함수와 유사하다. atol 함수는 현존하는 코드와의 호환성을 위해서 제공되었다; strtol이 더 안정적이다.

함수 : int atoi (const char *string)

이 함수는 long int 가 아닌 int 형으로 값을 반환한다는 점을 제외하고, atol 함수와 같다. atoi 함수는 또한 오래된 함수로 간주되므로 strtol 을 대신 사용하라.

이것은 정수로 이어진 문자열을 분석하고 그들의 합을 구하는 함수이다.

int sum_ints_from_string (char *string)
{
int sum = 0;
 
while (1) {
char *tail;
int next;
 
/* 앞이 공백으로 되어 있는 것을 건너뛴다. */
while (isspace (*string)) string++;
 
if (*string == 0)
break;
 
/* 공백이 제거되었으므로 숫자로 변경하자 */
errno = 0;
/* 그것을 분석한다. */
next = strtol (string, &tail, 0);
/* 만일 오버플로우가 발생하지 않았다면, 그것을 더해라 */
if (errno)
printf ("Overflow\n");
else
sum += next;
/* 다음 분석을 위해서 지금 현재까지 분석한 위치를 저장해놓는다. */
string = tail;
}
return sum;
}

 

14. 7. 2 플로트의 파싱

이곳에 있는 함수들은 'stdlib. h'에 선언되어 있다.

함수 : double strtol (const char *string, char **tailptr)

strtol ("문자열을 double 로") 함수는 문자열의 처음부분을 double형을 가진 플로팅-포인트 수로 변환한다. 이 함수는 다음의 경우에 문자열을 분해하려 시도한다.
연속적인 공백문자들. 공백문자(whitespace characters)들은 isspace 함수( 4. 1절 [Classification of Characters] 참조. ) 의 사용으로 알아낼 수 있다. 공백은 버려진다.
 
임의의 플러스나 마이너스 부호 ( '+' 또는 '-') , 소수점(보통 '. ')을 포함하고 있는 공백 없는 숫자열, 그러나 소수점의 위치에 의존한다. (19. 6절 [Numeric Formatting] 참조. )
문자 임의의 부호 'e' 또는 'E', 그리고 숫자들의 열로 구성된 지수부분.
문자열의 나머지 문자. 만약 tailptr이 널 포인터가 아니라면, 스트링의 tail 포인터를 *tailptr에 저장된다.
 
만일 그 문자열이 비어있거나, 오직 공백만 있거나, 또는 플로팅-포인트로 판단할만한 문자열을 포함하지 않다면, 아무런 변환이 일어나지 않는다. 이 경우, strtod는 0을 반환하고, *tailptr에 반환된 값은 문자열의 값이다.
표준 "C" 와는 다른 지역이라면, 이 함수는 그 지역의 구문에 의존하는 부가적 작동들을 인식할 것이다.
 
만일 그 문자열이 플로팅-포인트 숫자를 이루기 위한 유용한 구문을 갖고 있지만, 그 값을 오버플로우 때문에 표현할 수 없다면, strtod는 그 값의 부호에 의존해서, 음이나 양의 HUGE_VAL을 반환한다. ( 13장. [Mathematics] 참조. ). 유사하게, 만일 그 값이 언더플로우(underflow) 때문에 표현할 수 없다면, strtod는 0을 반환한다. 그것은 또한 오버플로우나 언더플로우의 경우에 errno를 ERANGE 로 설정한다.

함수 : double atof (const char *string)

이 함수는 오버플로우와 언더플로우 에러를 검출할 필요가 없다는 점을 제외하고는, strtod 함수와 유사하다. atof 함수는 현존하는 코드와의 호환성을 위해서 제공된다; strtod를 사용하는 것이 더 안전성이 있다.


목차 이전 : 13. 수학함수 다음 : 15. 탐색과 정렬

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

16. 형식일치( Pattern Matching )  (0) 2007.12.22
15. 탐색과 정렬  (0) 2007.12.22
14. 저수준 연산 함수들  (0) 2007.12.22
13. 수학 함수  (0) 2007.12.22
12. Low-Level Terminal Interface  (0) 2007.12.22
11. Sockets  (0) 2007.12.22
Comment 0 Trackback 0
Top

13. 수학 함수

목차 이전 : 12. 저수준 터미널 인터페이스 다음 : 14. 저수준 연산함수들


13 수학 함수

이장은 삼각함수와 같은, 수학적 계산을 수행하는 함수들에 대해서 설명한다. 이들 함수들중 대부분은 헤더파일 'math. h'에 선언되어 있다.

이 함수들 모두는 플로팅-포인트 수를 인수로 받아들여 계산하고, double형으로 결과를 반환한다. 즉, float 와 long double 값들을 계산하는 부가적함수가 될 것이다. 예를 들어, cosf 와 cosl은 float 와 long double 인수들로 수행하는, cos 함수의 변형이다. 동시에, 당신이 그들 예약어를 당신이 임의의 목적으로 사용하는 것을 피하라. 1. 3. 3절 [Reserved Names] 참조.


13. 1 정의구역과 범위 에러들

이 절에서 설명된 함수들의 대부분은 실수(read nunbers)의 부분집합인 정의구역(domain)에 관하여 수학적으로 정의되어 있다. 예를 들어, acos 함수는 -1 과 1 사이의 정의구역으로 정의된다. 만일 당신이 그 함수에서 정의되어 있는 정의구역 밖의 값을 인수로 사용한다면, 그 함수는 errno를 EDOM으로 설정해서 정의구역 에러가 났음을 지적한다. IEEE floating point 를 지원하는 컴퓨터에서, error 로 EDOM을 보고하는 함수들은 또한 NaN 도 반환한다.

이들 함수중 어떤 것은 그들의 정의구역에서 복잡한 값으로 결과를 낸다. 이것과 가장 유사한 예는 음수에 루트를 씌운 것의 결과 값이다. 이 장에 있는 함수들은 오직 실수 인수만을 취하고, 실수 값만을 반환한다. 그래서 만일 실수가 아닌 값이면, 이것을 정의구역 에러로 취급한다.

연관성 있는 문제로 함수의 결과를 플로팅 포인트로 나타낼 수 없는 것이 있다. 만일 정확하게 나온 결과 값의 크기가 나타내기에 너무 크면, 그 함수는 범위 에러임을 지적하기 위해서 ERANGE로 errno를 설정하고, 특별하게 매우 큰 값을 반환하거나( 매크로 HUGE_VAL ), 그것의 부정(negation)의 값을(-HUGE_VAL)을 반환한다. 만일 결과의 크기가 너무 작으면, 0의 값을 대신 반환한다. 이 경우, ERANGE의 에러가 날수도, 또는 나지 않을 수도 있다.

정의구역과 범위에러를 체크하기 위한 좋은 방법은 우선 당신이 수학적 함수를 호출하기 전에 errno를 0으로 설정한 다음, 나중에 errno를 테스트 해보는 것이다. errno를 이같이 사용한 결과로, 당신이 에러들을 체크하면, 그 수학함수를 재진입해서 사용할 수 없다.

수학적 함수들의 어떤 것도 정의구역이나 범위에러로 인해서 결코 신호를 발생시키지 않는다. 이 특별함이 의미하는 것은 이들 함수들의 사용으로는 SIGEPE신호들의 발생을 당신이 볼 수 없다는 것이다. ( 21장 [Signal Handling] 신호에 대한 정보 참조. )

매크로 : double HEGE__VAL

특별히 매우 큰 값을 나타내는 표현. IEEE 플로팅 포인트 형식을 지원하는 기계에서, 그 값은 "무한대"이다. 다른 기계들에서는, 그것은 나타낼 수 있는 가장 큰 양의숫자를 의미한다. 이 매크로의 값은 여러 가지 수학적 함수들이 발생시킬 수 있는 오버플로우 상황에서 반환값을 나타내기 위해서 사용된다.
 
플로팅 포인트 표현과 그 제한에 대한 자세한 정보는, A. 5. 3. 2절 [Floating Point Parameters] 를 참조하라. 매크로 DBL_MAX는 수학적 함수들로 에러를 테스트하기보다는 다른 용도로 많이 사용한다면 HUGE_VAL보다는 사용하기에 더 적당할 것이다.


13. 2 삼각 함수

이들은 sin, cos, 과 tan 함수들과 유사하다. 이들 함수에 주어질 인수들은 라디안(radians : 호도)의 단위로 주어진다. pi 라디안은 180도와 같다는 것을 상기하라. math 라이브러리에서 pi를 위한 심볼 상수를 정의하지 않았지만, 당신이 만일 필요하다면 당신 스스로 정의할 수 있다.

#define PI 3. 14159265358979323846264338327

당신은 또한 acos(-1. 0)의 표현식으로 pi의 값을 계산할 수 있다.

함수 : double sin (double x)

이 함수는 라디안의 값으로 주어진 x의 sine값을 반환한다. 반환 값은 -1과 1의 범위안에 있다.

함수 : double cos (double x)

이 함수는 라디안의 값으로 주어진 x 의 cosine 값을 반환한다. 반환 값은 -1 과 1 의 범위안에 있다.

함수 : double tan (double x)

이 함수는 라디안의 값으로 주어진 x 의 tangent 값을 반환한다.
다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

ERANGE

수학적으로, tangent 함수는 pi/2 의 홀수배( odd multiples ) 의 특성들을 가진다. 만일인수 x 가 이들 특성들 중 하나에 너무 근접해 있다면, tan은 errno를 ERANGE로 설정하고 음이나 양인 HUGE_VAL 중의 하나를 반환한다.


13. 3 역삼각 함수들

이 절에서는 sine, cosine, tangent 함수들의 역함수인 arc sine, arc cosine, arc tangent 함수들을 설명하고 있다.

함수 : double asin (double x)

이 함수는 x(sine x 의 결과 값인 )의 arc sine의 값을 계산한다. 결과 값은 라디안의 단위이다. 수학적으로, 무한대의 많은 값들이 존재한다; 실제로는 -pi/2 와 pi/2(포함해서) 사이의 하나가 반환된다. 만일 x가 범위밖에 존재하면, asin은 실패하고, errno는 EDOM으로 설정된다. arc sine 함수는 -1 에서 1 사이의 정의구역을 가지도록 정의되었다.

함수 : double acos (double x)

이 함수는 x(cosine x의 결과 값인 )의 arc cosine의 값을 계산한다. 이 값은 라디안의 단위이다. 수학적으로 무한대의 값이 존재한다; 실제로 0 과 pi(포함해서) 사이의 하나가 반환된다. 만일 x 가 범위밖에 존재하면, acos는 실패하고, errno를 EDOM으로 설정한다. arc cosine 함수는 오직 -1에서 1사이의 정의구역을 가지도록 정의됐다.

함수 : double atan (double x)

이 함수는 x(tangent x 의 결과 값인 )의 arc tangent의 값을 계산한다. 이 값은 라디안의 단위이다. 수학적으로 무한대의 값이 존재한다; 실제로는 -pi/2 와 pi/2(포함해서) 사이의 값중 하나가 반환된다.

함수 : double atan2 (double y, double x)

이 함수는 두 개의 인수를 가진 arc tangent 함수이다. 이것은 두 인수의 부호가 결과 값의 4분구간(quadrant)을 결정하는데 사용되고, x의 값으로 0의 사용이 허용된다는 것을 제외하고는, y/x의 arc tangent 값을 계산하는 것과 유사하다. 반환 값은 라디안으로 주어진고, -pi 와 pi를 포함한 그 사이의 범위를 가진다.
 
만일 x 와 y 가 평면에 위치한 좌표라면, atan2는 x 를 축으로 하고, 원점으로부터 그 점까지 사이의 부호화된 각도를 반환한다. 그래서 atan2는 데카르트 좌표를 극 좌표로 변경하는데 유용하다. ( radial 좌표를 계산하기 위해서는, hypot를 사용하라; 13. 4절 [Exponents and Logarithms] 참조. ) atan2 함수는 x 와 y 가 둘다 0이면 EDOM으로 errno를 설정한다; 반환 값은 이 경우에 정의되지 않았다.


13. 4 지수함수와 대수

함수 : double exp (double x)

exp 함수는 e( 자연대수에 기초한 )의 x 승의 값을 반환한다. 만일 결과의 크기가 너무 커서 나타낼 수 없으면, 그 함수는 실패하고, errno를 ERANGE 로 설정한다.

함수 : double log (double x)

이 함수는 x 의 자연로그를 반환한다. 수학적으로 정확하게, C로는 대략적으로 exp (log (x)) 는 x 와 같다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EDOM

인수 x 가 음수이다. log 함수는 양의 인수가 주어져야지만 실수 결과값을 반환하도록 정의되어있다.

ERANGE  : 인수가 0이다. 0의 로그값은 정의되지 않았다.

함수 : double log10 (double x)

이 함수는 10을 밑으로 하는 x의 로그값을 반환한다. 밑의 값이 다르다는 것을 제외하고는, log 함수와 유사하다. 실제로, log10 (x)는 log (x) / log (10) 과 같다.

함수 : doule pow (double base, double power)

이것은 base의 power승을 반환하는 일반적 지수 함수이다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EDOM

base 인수가 음수이고 power는 정수값이 아니다. 수학적으로, 이 경우에 결과가 복잡하게 나올 것이다.

ERANGE : 결과에서 언더플로우나 오버플로우 상황이 검출되었다.

함수 : double sqrt (double x)

이 함수는 x의 음이 아닌 루트의 값을 반환한다. sqrt 함수는 만일 x가 음수이면 실패하고, errno를 EDOM으로 설정한다. 수학적으로, 루트값은 복잡한 수이다.

함수 : double cbrt(double x)

이 함수는 x 의 세제곱근의 값을 반환한다. 이 함수는 실패할 수 없다; 모든 표현 가능한 실수의 값은 표현 가능한 세제곱근의 값을 가질 수 있다.

함수 : double hypot (double x, double y)

hypot 함수는 sqrt ( x*x + y*y )의 값을 반환한다. (이것은 x 와 y길이의 두변을 가진 직각 삼각형의 빗변의 길이이거나, 원점으로부터 점(x, y) 까지의 거리이다. ) cabs함수에 대한 것은 14. 3절 [Absolute Value] 참조.

함수 : double expml (double x)

이 함수는 exp (x) - 1 과 동일한 값을 반환한다. 그것은 심지어 x가 0에 근접한 값이라 할지라도 정확하게 계산되어진다. _ exp (x) -1은 두수의 차이가 거의 같다면 부정확하게 되어질 것이다.

함수 : double loglp (double x)

이 함수는 log (1 + x)와 동등하다. 이 함수는 심지어 x의 값이 0에 근접해 있을지라도 정확하게 계산되어진다.


13. 5 쌍곡선 함수들

이 절에 있는 함수들은 지수 함수들과 연관이 있다; 13. 4절 [Exponents and Logarithms] 참조.

함수 : double sinh (double x)

sinh 함수는 수학적으로 exp(x) - exp(-x)/2 로 정의된, x의 쌍곡선 sine을 반환한다. 만일 x의 값이 너무 크면 그 함수는 실패하고, errno를 ERANGE 로 설정한다; 그것을 오버플로우의 발생이라고 한다.

함수 : double cosh (double x)

cosh 함수는 수학적으로 exp(x)-exp(-x)/2 로 정의된, x의 쌍곡선 cosine을 반환한다. 만일 x의 값이 너무 크면, 함수는 실패로 끝나고, errno를 ERANGE로 설정한다; 그것을 오버플로우의 발생이라고 한다.

함수 : double tanh (double x)

이 함수는 sinh(x)/cosh(x)이라는 수학적 정의를 가진, x 의 쌍곡선 tangent x를 반환한다.

함수 : double asinh (double x)

이 함수는 쌍곡선 sine이 x 인 값_x의 역 쌍곡선 sine의 값을 반환한다.

함수 : double acosh (double x)

이 함수는 쌍곡선 cosine 이 x 인 값_x의 역 쌍곡선 cosine의 값을 반환한다. 만일 x 가 1보다 작으면, acosh는 HUGE_VAL을 반환한다.

함수 : double atanh (double x)

이 함수는 쌍곡선 tangent가 x인_x의 역 쌍곡선 tangent의 값을 반환한다. 만일 x의 절대값이 1 과 같거나, 1보다 크면, atanh는 HUGE_VAL을 반환한다.


13. 6 의사-난수

이 절은 의사-난수를 발생시키는 GNU 함수들을 설명하고 있다. 그 숫자들은 실제로는 난수가 아니다. 특별히, 그들은 정해진 주기로 반복적으로 숫자열을 형성하는데, 그 주기는 당신이 원래의 목적을 위해서는 무시 가능한, 그렇게 큰 주기이다. 난수 발생기는 다음 난수를 계산하기 위해 사용할 seed를 항상 기억하고 또한 새로운 seed를 계산하기 위해 작업한다.

비록, 발생된 숫자가 프로그램의 처음 실행에서는 예측할 수 없게 보이지만, 숫자들의 열(sequence)은 다음실행에서도 처음과 정확히 동일한 값이 나온다. 이것은 처음 seed값이 항상 동일하기 때문이다. 이것은 프로그램을 디버깅할 때 편리하지만, 만일 당신이 예측 불가능한 행동을 하도록 프로그램을 만들기 원한다면 전혀 도움이 되지 못한다. 만일 당신이

단지 의사-난수가 아닌, 진짜 난수를 원한다면, 현재의 시간을 기반으로 해서 seed를 정하라

당신이 난수를 발생시키기 위해서는 동일한 seed 값으로 정해서 다른 컴퓨터 상에서도 반복적인 임의의 숫자열을 얻을 수 있다. seed값으로는 아무런 표준이 없다. 동일한 seed를 다른 C 라이브러리나, 또는 다른 CPU 타입 상에서 사용하면 당신은 다른 난수들을 얻을 것이다.

GNU 라이브러리는 표준 ANSI C 난수 함수와 더불어 BSD시스템에서 기원된 다른 함수들도 지원한다. 우리는 표준 함수인 rand 와 srand를 사용하기를 권한다.

 

13. 6. 1 ANSI C 난수 함수들

이 절은 ANSI C 표준의 일부분인 난수 함수들을 설명한다. 이들을 사용하기 위해서, 당신은 당신의 프로그램에 헤더파일 'stdlib. h'를 포함시켜야 한다.

매크로 : int RAND__MAX

이 매크로의 값은 rand 함수에 의해 반환된 최대 가능한 값을 표현하기 위한 정수 상수 표현이다. GNU 라이브러리에서, 그것은 32비트에서 표현 가능한 가장 큰 양의 정수값인 037777777이다. 다른 라이브러리에서는 그것은 32767로 작을 것이다.

함수 : int rand ()

rand함수는 차례로 다음 의사-난수를 반환한다. 그 값은 0과 RAND_MAX 사이의 범위이다.

함수 : void srand (unsigned int seed)

이 함수는 의사-난수들의 새로운 열(series)을 위해서 seed로 seed를 만든다. 만일 당신이 srand로 seed를 정하기 전에 rand를 호출하면, 그것은 디폴트값 seed인 1을 사용한다. 실제로 난수를 만들기 위해서는 (단지 의사-난수가 아닌), srand(time(0)) 을 사용하라.

 

13. 6. 2 BSD 난수 함수들

이 절은 BSD에서 기인된 난수 발생 함수들에 대해서 설명한다. GNU C 라이브러리에서 이들 함수를 사용하는 것은 아무런 이득이 없다; 우리는 오직 BSD와의 호환성을 위해서 그들을 지원할 뿐이다. 이들 함수들을 위한 프로토타입은 'stdlib. h'이다.

함수 : long int random ()

이 함수는 차례로 다음 의사-난수를 반환한다. 반환된 값의 범위는 0과 RAND_MAX사이다.

함수 : void srandom (unsigned int seed)

srandom 함수는 정수 seed를 기초로 하는 현재의 난수를 위해서 seed를 설정한다. 만일 당신이 1의 값으로 seed를 공급하면, 이것은 디폴트 난수들을 집합을 재생산하는 결과를 낳는다. 진짜 난수(단지 의사 난수가 아닌)를 만들기 위해서는, sramdom(time(0))을 사용하라.

함수 : void *initstate(unsigned int seed, void *state, size_t size)

initstate 함수는 난수 발생기 상황을 초기화하기 위해서 사용된다. state 인수는 상황에 대한 정보를 저장하고 있는 size 바이트의 배열이다. size는 적어도 8바이트를 가져야만 하고, 최상의 크기는 8, 16, 32, 64, 128, 그리고 256 이다. 배열은 클수록 좋다. 반환 값은 상황 정보 배열의 전의 값이다. 당신은 그 상황을 재저장하기 위해서 setstate의 인수로서 나중에 이 값을 사용할 수 있다.

함수 : void *setstate (void *state)

stetstate 함수는 난수 상황에 대한 정보 state 를 재 저장한다. 인수는 initstate나 또는 setstate의 호출로 얻은 결과를 사용해야만 한다. 반환 값은 상황 정보 배열의 기존의 값이다. 당신은 그 상황을 재저장하기 위해서 stestate에 나중에 이 값을 인수로 사용할 수 있다.


목차 이전 : 12. 저수준 터미널 인터페이스 다음 : 14. 저수준 연산함수들

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

15. 탐색과 정렬  (0) 2007.12.22
14. 저수준 연산 함수들  (0) 2007.12.22
13. 수학 함수  (0) 2007.12.22
12. Low-Level Terminal Interface  (0) 2007.12.22
11. Sockets  (0) 2007.12.22
10. Pipes and FIFO  (0) 2007.12.22
Comment 0 Trackback 0
Top

12. Low-Level Terminal Interface

목차 이전 : 11. 소켓 다음 : 13. 수학함수


12 저수준 터미널 인터페이스

이 장은 터미널 디바이스의 여러 가지를 정하는 함수를 설명하고 있다. 당신은 이 함수들을 통해서 입력의 반향(echoing)을 없애는 것과 같은 일을 할 수 있고; 속도와 흐름제어와 같은 직렬통신 문자들을 설정할 수도 있고; 파일의 끝, 코멘트-라인 편집, 신호 보내기, 그리고 유사한 신호 함수들을 위해 사용된 문자들을 변경할 수도 있다.

이 장에 있는 함수들의 대부분은 파일 기술자 상에서 동작한다. 8장[Low-Level I/O] 에 파일 기술자가 무엇이고, 터미널 디바이스를 위해 파일 기술자를 어떻게 개방하는지에 대한 자세한 정보가 있다.


12. 1 터미널 확인하기

이 장에서 설명하고 있는 함수들은 터미널 디바이스에 해당하는 파일에서만 동작한다. 당신은 isatty 함수를 사용해서 터미널과 연관된 파일기술자인지 어떤지를 알아낼 수 있다. isatty 와 ttyname 함수들을 위한 프로토타입은 헤더파일 'unistd. h'에 선언되어 있다.

함수 : int isatty (int filedes)

이 함수는 만일 filedes가 터미널 디바이스와 연관된 파일 기술자이면 1을 반환하고, 그렇지 않으면 0을 반환한다. 만일 파일기술자가 터미널과 연관되어 있다면, 당신은 ttyname함수를 사용해서 그 연관된 파일 이름을 얻을 수 있다. 또한 ctermid함수를 참고하라, 그것은 24. 7. 1절 [Identifying the Termina. ] 에 있다.

함수 : char *ttyname(int filedes)

만일 파일 기술자 filedes 가 터미널 디바이스와 연관되어 있으면, ttynaem함수는 정적으로 할당되고, 널 문자로 끝나는 문자열에 터미널 파일의 파일이름을 저장하여 그것을 가리키는 포인터를 반환한다. 만일 파일 기술자가 터미널과 연관되지 않거나, 또는 그 파일이름을 알아낼 수 없다면, 널 포인터를 반환한다.


12. 2 입/출력 큐

이 절에 있는 많은 함수들은 터미널 디바이스의 입력과 출력 큐(queues)를 조회한다. 이들 큐는 입/출력 스트림들에 의해 실행된 버퍼링의 커널안에 있는 버퍼링의 한 형식이다. ( 7장 [I/O on Streams] 참조. )

터미널 입력 큐는 또한 선행입력(typeahead) 버퍼로써 사용되어진다. 그 큐는 터미널로부터 받아들여졌지만, 아직 어느 프로세스에 의해서도 읽혀지지 않은 문자들은 저장한다.

 
-- 역자주: typeahead : 어떠한 이유로 인하여 입력의 속도가 프로그램의 작업처리 속도보다 빠를 경우 아직 처리되지 못한 입력들은 잠시 내부에 있는 기억장치에 저장해두고 나중에 처리하는 방법

터미널의 입력 큐의 크기는 _POSIX_MAX_INPUT 과 MAX_INPUT 파라미터로 표현된다; 27. 6절 [Limits for Files] 참조. 만일 IXOFF 입력 모드 비트가 설정되어서 입력 흐름제어가 가능하다면( 12. 4. 4절 [Input Modes] 참조), 터미널 드라이버는 큐가 오버플로우가 나는 것을 방지하기 위해, 필요할 때 터미널로 STOP 와 START 문자들을 전송한다. 그렇지 않다면, 터미널로부터 너무 많은 입력이 쇄도할 경우 그 입력을 잃어버릴지도 모른다. ( 이런 상황은 손으로 타이핑을 통해 입력하는 것으로는 불가능하다. )

터미널 출력 큐는 입력큐와 같지만, 출력을 위해 쓰인다. 출력큐는 프로세스에 의해 출력되어 졌지만, 아직 터미널로 전송되지 않은 문자들을 저장하고 있다. 만일 IXON 입력 모드 비트( 12. 4. 4절 [Input Modes] 참조)가 설정되어서 출력 흐름 제어가 가능하다면, 터미널 드라이버는 멈춤을 지시하기 위해 터미널에서 보낸 STOP 문자들과 출력의 재전송에 따른다.

터미널의 입력큐의 소거(Clearing)란 받아들이기는 했지만 아직 읽혀지지 않은 문자들을 버리는 것을 의미한다. 유사하게, 터미널 출력큐를 소거하기란 출력됐지만, 아직 전송되지 않은 문자들을 버리는 것을 의미한다.


12. 3 입력의 두 가지 스타일: Canonical 또는 Not

POSIX시스템은 입력의 두 가지 기본 모드를 제공한다: 정규와 비정규(canonical and noncanonical)

정규(canonical) 입력 프로세싱 모드에서, 터미널 입력은 새줄문자('\n'), EOF, 또는 EOL 문자들로 종료되는 한 라인으로 처리된다. 어떤 입력도 사용자에 의해 한 라인 전체의 입력이 종료되기 전에 읽혀질 수 없고, read 함수는( 8. 2절 [I/O Primitives] 참조), 얼마나 많은 바이트가 요청되었는지에 상관없이, 많아야 오직 한 줄의 입력을 반환할 뿐이다.

정규입력 모드에서, 운영체제는 입력 편집 도구를 제공한다: ERASE 와 KILL 문자들은 텍스트의 현재의 줄에서 편집 명령을 수행하기 위해 특별하게 해석되어진다.

상수 _POSIX_MAX_CANON 과 MAX_CANON는 정규 입력의 한 줄에 나타낼 수 있는 최대 바이트 수를 한정한다. 27. 6절 [Limits for Files] 참조.

비정규입력( noncanonical input ) 프로세싱 모드에서, 문자들은 라인들로 묶여지지 않고, ERASE 와 KILL 프로세싱은 수행되지 않는다. 비정규입력에서 읽혀진 바이트들은 MIN 과 TIME을 설정함으로 인해서 제어된다. 12. 4. 10절 [Noncanonical Input] 참조.

대부분의 프로그램들이 정규입력을 사용하는 것은, 정규입력의 방법이 사용자에게 라인으로 입력을 편집할 수 있는 방법을 제공하기 때문이다. 비정규입력을 사용하는 보통의 이유는 프로그램이 단일-문자 명령들을 받아들이거나 또는 프로그램 자체가 편집도구를 제공할 때 사용된다.

정규 혹은 비정규의 선택은 구조체 struct termios의 멤버인 c_lflag에 ICANON 플로그에 의해 제어된다. 12. 4. 7절 [Local Modes] 참조.


12. 4 터미널 모드

이 절은 어떻게 입력과 출력이 수행되어지는지를 제어하는 다양한 터미널 속성들은 설명하고 있다. 이곳에서 설명한 함수, 자료구조, 그리고 기호 상수들은 모두 헤더파일 'termios. h'에 선언되어 있다.

 

12. 4. 1 터미널 모드 데이터 타입들

터미널 속성의 전부는 구조체 struct termios 에 저장되어 있다. 이 구조체는 속성들을 읽고, 설정하기 위한 함수 tcgetattr 과 tcsetattr에서 사용된다.

데이터 타입 : struct termios

터미널의 모든 입출력 속성을 기록하는 구조체. 이 구조체는 적어도 다음과 같은 멤버들을 포함하고 있다.

tcflag_t c_iflag

입력 모드를 위한 플래그들을 정하는 비트마스크; 12. 4. 4절 [Input Modes] 참조.

tcflag_t c_oflag

출력모드를 위해 플래그들을 정하는 비트마스크; 12. 4. 5절 [Output Modes] 참조.

tcflag_t c_cflag

제어모드를 위해 플래그들을 정하는 비트마스크; 12. 4. 6절 [Control Modes] 참조.

tcflag_t c_lflag

로컬모드를 위해 플래그들은 정하는 비트마스크; 12. 4. 7절 [Local Modes] 참조.

cc_t c_cc[NCCS]

다양한 제어 함수들과 연관된 문자들을 정하는 배열; 12. 4. 9절 [Special Characters] 참조.

구조체 struct termios 는 또한 입력과 출력 전송 속도를 부호화(encode)한 멤버들을 갖고 있지만, 아직 설명하지 않았다. 12. 4. 8절 [Line Speed]를 참조로 해서 어떻게 속도 값을 시험하고 저장하는지를 살펴보라.

다음절에서 구조체 struct termios 의 멤버들에 대한 자세한 설명을 할 것이다.

데이터 타입 : tcflag__t

unsigned integer 형으로, 터미널플래그들을 위한 다양한 비트마스크를 나타내는데 사용한다.

데이터 타입 : cc__t

unsigned integer 형으로 다양한 터미널 제어 함수들과 연관된 문자들을 나타내기 위해 사용한다.

매크로 : int NCCS

이 매크로의 값은 c_cc 배열에 있는 요소들의 개수이다.

 

12. 4. 2 터미널 모드 함수들

함수 : int tcgetattr(int filedes, struct termios *termios_p)

이 함수는 파일기술자 filedes와 연관된 터미널 디바이스의 속성을 시험하는데 사용된다. 그 속성은 구조체 termios_p가 가리키는 곳으로 반환된다.
 
만일 성공하면, tcgetattr 은 0을 반환하고, 실패하면 -1을 반환한다.  
다음의 errno는 이 함수를 위해 정의된 에러상황이다.

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

ENOTTY : filedes 가 터미널과 연관이 없다.

함수 : int tcsetattr(int filedes, int when, const struct termios *termios_p)

이 함수는 파일기술자 filedes와 연관된 터미널 디바이스의 속성을 설정한다. 새로운 속성들은 termios_p가 가리키고 있는 구조체로부터 가져온다.
when 인수는 이미 큐된( 큐에 저장되어 있는 ) 입력과 출력을 어떻게 취급할 것인지를 정하는 것으로 다음 값들중 하나를 사용할 수 있다.

TCSANOW : 즉시 속성을 변경시켜라.

TCSADRAIN

큐에 저장된 출력이 쓰여질 때까지 기다린 후에 속성을 변경하라. 당신은 변경하는 파라미터가 출력에 영향을 미칠 때 이 옵션을 사용한다.

TCSAFLUSH : 이것은 TCSADRAIN과 같지만, 큐에 저장된 입력을 버린다.

TCSASOFT

이것은 위에 있는 어떤 것과도 덧붙여 사용할 수 있는 플래그 비트이다. 이것은 터미널 하드웨어에 대한 상황의 변경을 금지하기 위한 것이다. 이것은 BSD 확장이다; BSD가 아닌 시스템에서는 아무런 영향을 받지 않는다.
 
만일 이 함수가 터미널을 제어하고 있는 배경 프로세스로부터 호출된다면, 보통 프로세스 그룹 안에 있는 모든 프로세스들은 터미널에 쓰기를 시도하는 프로세스가 있을 때와 같은 방법으로, SIGTTOU 시그널을 보낸다. 만일 함수를 호출한 프로세스 자신이 SIGTTOU 신호를 무시하거나 블록하고 있다면 이 명령은 수행되어 지고, 아무런 신호도 보내지 않는다. 24장 [Job control] 참조. 만일 성공하면,
 
tcsetattr 은 0을 반환하고, 실패하면 -1을 반환한다. 다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

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

ENOTTY : filedes는 터미널과 아무런 연관이 없다.

EINVAL : when 인수값이 유용하지 않거나, termios_p인수에 잘못된 데이터가 잘못되었거나.

tcgetattr 과 tcsetattr 이 filedes를 터미널과 연관된 파일기술자로 정했다고 할지라도, 그 속성은 파일기술자의 것이 아니라 터미널 디바이스 그 자체의 속성이다. 이것은 변경한 터미널 디바이스의 속성들이 불변함을 의미한다; 만일 나중에 다른 프로세스가 터미널 파일을 개방하면, 그것은 심지어 개방한 파일 기술자로 아무 일도 하지 못할지라도 변경된 속성들을 보일 것이다. 유사하게, 만일 단일 프로세스가 동일한 터미널 디바이스에 다중 또는 복제된 파일기술자들을 가졌다면, 터미널 속성 변경은 이 파일기술자 모두의 입력과 출력에 영향을 미친다. 이 의미는, 예를 들어, 당신은 한 개의 파일 기술자만을 개방할 수 없거나, 또는 반향 모드(echoed mode)로 보통의 라인 버퍼의 입력을 터미널로부터 읽기 위한 스트림을 개방할 수 없다; 그리고 동시에 당신은 비반향 모드로 동일한 터미널로부터 단일 문자를 읽기 위해 사용되는 다른 파일기술자를 가진다. 대신에 당신은, 두 개의 모드 사이에 어떤 것이 전면이고, 어떤 것이 후면인지 정확히 해야한다.

 

12. 4. 3 적당하게 터미널 모드 설정하기

당신이 터미널 모드를 설정할 때, 당신은 첫째로 특별한 터미널 디바이스의 현재의 모드를 얻기 위해 tcgetattr을 호출하고, 그 다음 실제로 당신이 관심을 가진 모드들을 갱신하고, tcseattr에 그 결과를 저장한다.

속성들의 설정을 선택하기 위해 구조체 struct termios를 간단히 초기화하고 직접적으로 tcsetatt에 인수로 주는 것은 나쁜 방법이다. 당신의 프로그램은 이 매뉴얼에 문서화되지 않은 멤버들을 지원하는 시스템에서 지금으로부터 수년 후에 실행되어질지도 모른다. 이해할 수 없는 값들로 이들 멤버들은 설정하는 것을 피하기 위한 방법은 그들을 변경하는 것을 피하는 것이다. 즉, 다른 터미널 디바이스는 적당하게 설정된 다른 모드를 필요로 한다. 그래서 당신은 하나의 터미널 디바이스로부터 다른 터미널 디바이스로 그냥 속성들을 카피하는 것은 피해야한다.

한 멤버가 c_iflag, c_oflag 그리고 c_cflag 등의 독립적 플래그들의 집합일 때, 그들 전체 멤버들을 설정하려하는 것은 잘못된 생각이다, 왜냐하면, 특별한 운영체제는 그들 자신만의 플래그들을 갖고 있기 때문이다. 대신에, 당신은 멤버의 현재의 값들을 얻어서, 당신의 프로그램에서 다룰 수 있는 플래그만 변경하고, 다른 플래그들을 변경하지 않도록 해라. 이곳에 구조체 struct termios의 다른 데이터를 그대로 유지하고, 오직 한 플래그(ISTRIP)만을 어떻게 변경하는지에 대한 예가 있다.

int
set_istrip (int desc, int value)
{
struct termios settings;
 
if (tcgetattr (desc, &settings) < 0) {
perror ("error in tcgetattr");
return 0;
}
settings. c_iflag &= ~ISTRIP;
if (value)
settings. c_iflag |= ISTRIP;
if (tcgetattr (desc, &settings) < 0) {
perror ("error in tcgetattr");
return;
}
return 1;
}

 

12. 4. 4 입력 모드들

이 절은 입력 프로세싱의 저수준 관점을 완전히 제어하는 터미널 속성 플래그들은 설명하고 있다: 에러 핸들링, 멈춤 신호들, 제어 흐름, 그리고 RET 와 LFD 문자들.

이들 플래그 모드는 구조체 struct termios 의 c_iflag안의 비트들이다. 그 멤버는 정수이고, 당신은 오퍼레이터 &, | 그리고 ^ 를 사용해서 플래그들은 변경할 수 있다. c_iflag_instead를 위한 전체 값을 정하여 시도하지 말고, 나머지는 손대지 말고 남겨둬라. ( 12. 4. 3절 [Setting Modes] 참조. )

INPCK

만일 이 비트가 설정되면, 입력 패리티 체크가 가능하다. 만일 설정되지 않으면, 입력에서 패리티 에러가 났는지 체크하지 않는다; 즉 그 문자들은 그 어플리케이션에 간단히 주어진다. 입력 프로세싱에서 패리티 체크는 패리티 검출의 여부에 독립하여 있고, 터미널 하드웨어에서의 패리티 발생은 가능하다; 12. 4. 6절 [Control Modes] 참조. 예를 들어, 당신은 INPCK 입력 모드 플래그를 소거하고 입력에서 패리티 에러들은 버리기 위해 PARENB 제어 모드 플래그를 설정할 수 있지만, 여전히 출력에서 패리티 에러는 발생한다.
 
만일 이 비트가 설정되면, 패리티 에러가 발생했을 때 IGNPAR 이나 PARMRK 비트가 설정되었는지에 의존하여 무슨 일이 발생한다. 이 비트를 설정하지 않는다면, 패리티 에러를 위한 한 바이트를 '\0'으로 응용프로그램에 주어야 한다.

IGNPAR

만일 이 비트가 설정되면, 구성(framing) 이나 패리티 에러를 위한 바이트가 무시되어진다. 이것은 INPCK 가 설정되어야지 만 유용하다.

PARMRK

만일 이 비트가 설정되면, 패리티나 구성 에러가 있는 입력 바이트들이 프로그램에 표시된다. 이 비트는 INPCK 가 설정되고 IGNPAR이 설정되지 않았을 때 유효하다. 잘못된 바이트를 표시하는 방법은 두 개의 선행 바이트, 377과 0을 사용하는 것이다. 그래서, 프로그램은 터미널로부터 받은 잘못된 한 개의 바이트로부터 세 개의 바이트들을 읽게되는 것이다. 만일 유용한 바이트가 0377의 값을 가지고, ISTRIP( 밑을 보라. ) 이 설정되지 않는다면, 그 프로그램은 그것을 패리티 에러 표시와 혼동할 것이다. 그래서 유용한 바이트 0377은 두 개의 바이트로 프로그램에 주어진다, 즉 이 경우에는 0377 0377로. . .

ISTRIP

만일 이 비트가 설정되면, 유용한 입력 바이트들이 일곱 비트로 구성되어 있다; 그렇지 않다면, 여덟 비트 모두가 읽혀서 프로그램에서 사용된다.

IGNBRK

만일 이 비트가 설정되면, 멈춤(break)의 상황이 무시된다.
멈춤(break) 상황은 한 바이트보다 긴 0-값을 가진 비트들의 열들을 비동기적 직렬 데이터 전송의 방법으로 정의된다.

BRKINT

만일 이 비트가 설정되고, IGNBRK 가 설정되지 않는다면, 멈춤(break)의 상황은 터미널 입력과 출력의 큐를 소거하고, 터미널과 연관있는 전면 프로세스 그룹을 위해서 SIGINT 신호를 발생한다. 만일 BRKINT 나 IGNBRK 가 설정되지 않았다면, 멈춤(break) 상황은 만일 PARMRK가 설정되지 않으면, 단일 문자 '\0'로 응용프로그램에 주어진다, 그렇지 않으면, 세 개의 문자열 '\377', '\0', '\0'로 주어진다.

IGNCR

만일 이 비트가 설정되면, 캐리지반환 문자('\r')는 입력에서 버려진다. 버려진 캐리지반환은 당신이 RET 키를 칠 때 캐리지반환과 라인피드(linefeed) 이 둘을 보낸 터미널에서 유용하게 될 것이다.

ICRNL

만일 이 비트가 설정되고 IGNCR 이 설정되지 않으면, 입력으로 받은 캐리지반환문자를('\r') 새줄문자('\n')로해서 응용프로그램에 주어진다.

INLCR

만일 이 비트가 설정되면, 입력으로 받은 새줄문자('\n')는 캐리지반환문자('\r')로 응용프로그램에 주어진다.

IXOFF

만일 이 비트가 설정되면, 입력의 정지/시작의 제어가 가능하다. 즉, 컴퓨터가, 프로그램이 처리하는 속도보다 더 빠르게 데이터가 도착하는 것을 방지하기 위해 STOP과 START 문자들을 보내는 것이다. 입력 데이터를 생성하는 실제 터미널 하드웨어에서 STOP 문자에 응답하여 전송을 중단하고, START 문자에 응답하여 다시 전송을 재개한다. 12. 4. 9. 4절 [Strat/Stop Characters] 참조.

IXON

만일 이 비트가 설정되면, 출력의 시작/정지의 제어가 가능하다. 즉, 만일 컴퓨터가 STOP 문자를 받으면, START 문자를 받을 때까지 출력을 중단한다. 이 경우, STOP 과 START 문자들은 결코 응용프로그램에 주어지는 것이 아니다. 만일 이 비트가 설정되지 않았다면 START와 STOP는 원래의 문자들로 읽혀진다. 12. 4. 9. 4절 [Start/Stop Characters] 참조.

IXANY

만일 이 비트가 설정되면, 출력이 STOP를 통해 정지되어져 있을 때, 어느 문자를 가지고도 출력을 재개할 수 있다. 즉, 오직 START 문자만 출력을 재개하는 것이 아니라는 것이다.

IMAXBEL

만일 이 비트가 설정되면, 벨이 울리도록 터미널에 BEL문자( code 007)를 보내어 터미널의 입력 버퍼를 가득 채운다.

 

12. 4. 5 출력 모드들

이 절은 출력 문자들을 어떻게 해석하고 화면을 채울 것인지를 제어하는 터미널 플래그와 필드들에 대해 설명한다. 이들 모두는 구조체 struct termios의 c_oflag 멤버에 들어있다.

c_oflag 멤버 자신은 정수형이고, 당신은 그 플래그들과 필드들을 오퍼레이터 &, |, 그리고 ^를 사용해서 갱신할 수 있다. c_oflag의 전체의 값을 변경하려 시도하지 말고_대신에 오직 정해진 하나의 플래그만 변경하고 나머지는 손대지 말고 남겨둬라 ( 12. 4. 3절 [Setting Modes] 참조.

매크로 : int OPOST

이 비트가 설정되면, 출력 데이터는 터미널 디바이스에 적당하게 표시되는 그런 특별히 정해지지 않은 방법으로 처리된다. 이것은 새줄문자 ('\n')를 캐리지반환과 라인피드의 쌍으로 대치시켜 포함한다. 만일 이 비트가 설정되지 않는다면, 그 문자들은 그대로 전송된다.

다음 세 개의 비트들은 BSD를 위한 것으로, BSD 가 아닌 시스템에는 아무런 영향을 주지 않는다. 모든 시스템에서, 그들은 OPOST가 설정 되어있어야 효과를 발휘한다.

    매크로 : int ONLCR

    만일 이 비트가 설정되면, 출력에 나타난 새줄문자를 문자의 쌍(pair)인 캐리지반환과 라인피드로 변환한다.

    매크로 : int OXTABS

    만일 이 비트가 설정되면, 출력에 나타난 탭 문자들을 8칼럼의 탭을 구현하는 적당한 공백으로 변환한다.

    매크로 : int ONOEOT

    만일 이 비트가 설정되면, 출력에 나타나는 C-d 문자(code 004)들을 버린다. 이들 문자들은 dial-up 터미널의 연결을 단절시키기 때문이다.

 

12. 4. 6 제어 모드들

이 절은 비동기 직렬 데이터 전송에 관계된 제어 파라미터인 터미널 플래그와 필드들을 설명한다. 이들 플래그들은 터미널 포트(네트웍에 연결된 가상-터미널처럼)의 다른 종류에는 통하지 않을지도 모른다. 이들 모두는 구조체 struct termios의 c_cflag 멤버에 존재한다.

c_cflag 멤버 자체는 정수형으로, 당신은 그 플래그와 필드들을 오퍼레이터 &, |, 그리고 ^ 을 사용해서 갱신할 수 있다. c_cflag의 전체 값들을 정하려 시도하지 말고, 대신에 오직 정해진 플래그들만 변경하고, 나머지는 손대지 말고 남겨둬라(12. 4. 3절 [Setting Modes] 참조. )

CLOCAL

만일 이 비트가 설정되면, 터미널이 "국부적으로" 연결되어 있고, 모뎀 상태 라인들은( 캐리어 검출과 같은)무시됨을 의미한다. 만일 이 비트가 설정되지 않고 당신이 O_NONOBLOCK 플래그를 설정하지 않고 open을 호출하면, open은 모뎀이 연결될 때까지 블록 된다.
 
만일 이 비트가 설정되지 않고, 모뎀 연결이 끊어지면, SIGHUP 신호를 터미널(만일 그것이 하나를 가진다면)을 위한 제어 프로세스 그룹에 보낸다. 보통, 이것은 탈출(exit) 하려는 프로세스에 때문이다; 21장 [Signal Handling] 참조. 연결이 끊어진 후에 터미널로부터 읽기는 파일끝 상황을 발생하고, 쓰기는 반환될 EIO 에러를 발생한다. 터미널 디바이스는 반드시 닫혀져야 하고, 그 상황을 소거하기 위해 재개방되어져야 한다.

HUPCL

만일 이 비트가 설정되면, 모든 프로세스가 폐쇄된 파일인 터미널 디바이스를 갖거나, 빠져나갈 때 모뎀 단절(disconnect)이 발생한다.

CREAD

만일 이 비트가 설정되면, 입력이 터미널로부터 읽혀질 수 있다. 그렇지 않다면, 입력은 그것이 도착했을 때 버려진다.

CSTOPB

만일 이 비트가 설정되면, 두 개의 stop 비트가 사용된다. 그렇지 않다면 오직 한 개의 stop 비트가 사용된다.

PARENB

만일 이 비트가 설정되면, 패리티 비트의 생성과 검출이 가능하게 된다. 12. 4. 4절 [Input Modes] 를 참조로 입력 패리티 에러가 어떻게 다루어지는지를 보아라.
만일 이 비트가 설정되지 않으면, 출력 문자들에 패리티 비트가 더해지지 않고, 입력 문자들에서는 패리티에러를 체크하지 않는다.

PARODD

이 비트는 PARENB가 설정됐을 때만 유용하다. 만일 PARODD가 설정되면, 홀수 패리티가 사용되고, 그렇지 않으면 짝수 패리티가 사용된다.
제어 모드 플래그들은 문자당 비트들의 개수를 나타내는 필드를 갖고 있다. 당신은 그 값을 추출하기 위해서 CSIZE 매크로를 사용할 수 있다. 이처럼 : settings. c_cflag & CSIZE

CSIZE : 이것은 문자당 비트들의 개수를 위한 마스크이다.

    CS5 : 바이트당 다섯 비트를 정한다.

    CS6 : 바이트당 여섯 비트를 정한다.

    CS7 : 바이트당 일곱 비트들을 정한다.

    CS8 : 바이트당 여덟 비트들을 정한다.

CCTS_OFLOW

이 비트가 설정되면, CTS와이어(RS232 프로토콜)에 기반한 출력의 흐름제어가 가능하다.

CRTS_IFLOW

이 비트가 설정되면, RTS 와이어(RS232 프로토콜)에 기반한 입력의 흐름제어가 가능하다.

MDMBUF

이 비트가 설정되면, 출력의 캐리어-기반 흐름제어가 가능하다.

 

12. 4. 7 국소 모드들

이 절은 구조체 struct termios 의 c_lflag 멤버의 플래그들을 설명한다. 이들 플래그들은 일반적으로 12. 4. 4절 [Input Modes] 에서 설명한, 모드 플래그들보다는 반향, 신호들, 그리고 정규와 비졍규입력의 선택 등과 같은 입력 프로세싱의 고-수준 관점을 제어한다.

c_flag 멤버 그 자체는 정수형이고, 당신은 오퍼레이터 &, |, 그리고 ^를 사용해서 그 플래그들과 필드를 변경할 수 있다. c_lflag의 전체 값들을 정하려 시도하지 말고 대신에, 정해진 플래그들만 변경하고, 다른 나머지 것들에는 손대지 말라.

ICANON

이 비트가 설정되면, 정규입력 프로세싱 모드로 된다. 그렇지 않다면, 입력은 비정규 모드로 처리된다. 12. 3절 [Canonical or Not] 참조.

ECHO : 이 비트가 설정되면, 입력문자가 반향된다.

ECHOE

이 비트가 설정되면, 스크린의 현재의 라인에 있는 마지막 문자를 지우는 역할을 하는 ERASE 문자를 사용 할 때 그 마지막 문자를 화면상에서 실제로 없앰으로서 사용자에게 문자가 실제로 지워졌음을 확인시킨다. 그렇지 않다면 지워진 문자는 다시 반향된다( 프린팅 터미널에 적당한). 이 비트는 오직 앞에 표시된 것만을 제어한다. ECHOE를 사용하지 않고도, ICANON 비트는 ERASE문자를 실제로 인식하고 입력을 지우는 것을 그 자체에서 제어하고 있다.

ECHOK

이 비트는 KILL 문자에 대한 표시를 가능하게 한다. 이것을 할 수 있는 두 가지 방법이 있다. KILL 문자가 눌려진 곳의 전체 라인을 화면에서 지우는 것이 좀더 나은 방법이다. 좀더 안 좋은 방법은 KILL 문자를 반향한 후에 새줄로 옮기는 것이다. 어떤 시스템은 한가지를 허용하고, 어떤 시스템은 다른 하나를 허용하고, 어떤 것은 당신에게 두가지중 하나를 선택하도록 허용한다. 이 비트가 설정되지 않으면, KILL 문자는 KILL 문자가 존재하지 않는 것처럼 단지 그것을 반향한다. 그러면 전의 입력을 지웠던 KILL 문자를 기억하는 것은 사용자의 몫이다; 화면에서 아무런 표시가 없기 때문이다. 이 비트는 오직 전의 표시된 것을 제어한다. ICANON 비트는 그 자체로 KILL 문자를 실제로 인식하고, 입력을 지운다.

ECHONL

이 비트가 설정되고 ICANON 비트가 또한 설정되면 새줄 ('\n') 문자는 심지어 ECHO 비트가 설정되지 않았을 때도 반향된다.

ISIG

INTR, QUIT, 그리고 SUSP 문자들을 인식하는지의 여부에 대한 제어 비트이다. 이 문자들과 연관된 함수들은 이 비트가 설정됐을 때만 수행된다. 규정이나 비규정 입력에 대한 것은 이들 문자들을 해석하는데 아무런 영향이 없다. 당신은 이들 문자들의 인식을 불가능할 때 주의해서 사용해야한다. 사용자에 의해 인터럽트될 수 없는 프로그램은 사용자에게 매우 불친절한 것이다. 만일 당신이 이 비트를 소거하면, 당신의 프로그램은 사용자에게 이들 문자들과 연관있는 신호를 보내도록 허용하거나, 그 프로그램으로부터 탈출하는 인터페이스를 제공해야한다. 12. 4. 9절 [Signal Characters] 참조.

IEXTEN

이 비트는 ISIG와 유사하지만, 특별한 문자들에 정의된 제어실행에 대한 것이다. 만일 이 비트가 설정되면, ICANON 과 ISIG 국소 모드 플래그와 IXON과 IXOFF 입력모드 플래그를 위한 디폴트 동작을 무효로 할 것이다.

NOFLSH

보통, INTR, QUIT, 그리고 SUSP 문자들은 터미널의 입력과 출력큐를 소거하게 한다. 만일 이 비트가 설정되면 그 큐들은 소거되지 않는다.

TOSTOP

이 비트가 설정되고 시스템이 작업 제어를 지원하면, SIGTTOU신호가 터미널에 쓰기를 시도하는 배경 프로세스에 의해 발생되어진다. 24. 4절 [Access to the Terminal] 참조.

다음 비트들은 BSD 확장이다. GNU 라이브러리는 당신이 그들을 요청하는 어느 시스템 상에서도 사용하도록 이들 심볼들을 정의했지만, BSD 시스템과 GNU시스템을 제외한 다른 시스템에는 아무런 영향을 미치지 않는 비트 설정이다.

ECHOKE

BSD 시스템에서, 이 비트는 ECHOK를 설정할 때, KILL 문자를 표시하는 두 가지 방법중 하나를 선택하게 한다. 만일 ECHOKE 가 설정되면, KILL문자는 화면에서 라인 전체를 지우고 그렇지 않으면 KILL문자는 화면의 다음 라인으로 옮긴다. ECHOKE의 설정은 ECHOK가 설정됐을 때만 유효하다.

ECHOPRT

이 비트는 하드카피 터미널을 조정하는 방법으로 ERASE 문자의 표시를 가능하게 한다.

ECHOCTL

만일 이 비트가 설정되면, 해당하는 텍스트 문자가 따르는 control문자를 '^'표시로 반향한다. 그래서 control-A 는 '^A'처럼 나타난다.

ALTWERASE

이 비트는 WERASE 문자가 지울 것인지 결정한다. 단어의 시작점에서 뒤쪽으로 문자를 지운다. 어디에서 단어를 시작할 것인지 의문이 생긴다. 만일 이 비트가 설정되면, 단어의 시작점은 공백문자 다음의 비공백 문자가 된다. 만일 이 비트가 설정되지 않으면, 단어의 시작점은 영숫자 문자이거나, 또는 그들이 없는 문자 다음의 underscore이다.

FLUSHO

이 비트는 사용자가 DISCARD 문자를 입력할 때 토글 된다. 이 비트가 설정되어 있는 동안에, 모든 출력은 버려진다. 12. 4. 9. 5절 [Othr Special] 참조.

NOKERNINFO

이 비트의 설정은 STATUS 문자의 처리가 불가능하도록 한다. 12. 4. 9. 5절 [Other Special] 참조.

PENDIN

이 비트가 설정되면, 다시 프린트할 필요가 있는 입력 라인이 있음을 알린다. REPRINT문자를 치면 이 비트가 설정된다. 그 비트는 재프린트가 종료될 때까지 설정상태가 지속된다. 12. 4. 9. 2절 [BSD Editing] 참조.

 

12. 4. 8 라인 속도

터미널 라인 속도는 터미널 상에서 얼마나 빨리 데이터를 읽고 쓸 수 있는지를 컴퓨터에게 알린다. 만일 터미널이 직렬 라인으로 연결되면, 터미널 속도를 그 라인의 실제 속도에 맞도록 지정해야지, 터미널 자신의 임의대로 그 속도를 지정할 수 없고, 만약 그렇게 했다면 통신은 단절된다. 실제 직렬 포트는 오직 어떤 표준 속도만을 받아들인다. 어떤 특별한 하드웨어는 심지어 모든 표준 속도를 지원하지 않을 것이다. 제로(zero)로 속도를 정하는 것은 연결된 상태를 끊고 모뎀 제어 신호를 끄는 것이다.

만일 터미널이 직렬 라인이 아니라면(예를 들어, 그것이 네트웍 연결이라면), 라인의 속도는 실제 데이터 전송속도에 영향을 받지 않지만, 어떤 프로그램에서는 필요한 채워넣기(padding)의 양을 결정하기 위해 그것을 사용할 것이다. 그것은 실제 터미널의 속도에 대응되는 라인 속도 값을 정하기에 가장 좋지만, 당신은 채워넣기(padding)의 다양한 양을 다양한 값으로 안전하게 시험해야 한다.

각 터미널을 위한 두 개의 라인 속도가 있는데, 입력을 위한 것과 출력을 위한 것이다. 당신은 그들을 독립적으로 정할 수 있지만, 대부분의 터미널에서는 동일한 속도를 사용한다.

속도 값은 구조체 struct termios 에 저장되어 있지만, 직접적으로 구조체 struct termios안에 있는 그들을 직접적으로 억세스 하려 시도하지 말라. 대신에, 당신은 그들을 읽고 저장하기 위해서 다음 함수를 사용하라.

함수 : speed_t cfgetospeed(const termios *termios_p)

이 함수는 구조체 *termios`p안에 저장되어 있는 출력 라인 속도를 반환한다.

함수 : speed_t cfgetispeed (const struct termios *termios_p)

이 함수는 구조체 *termios`p안에 저장되어 있는 입력 라인 속도를 반환한다.

함수 : int cfsetospeed (struct termios *termios_p, speed_t speed)

이 함수는 출력 속도로 *termios`p 에 speed를 반환한다. 보통의 반환 값은 0이고, 에러가 발생하면 -1을 반환한다. 만일 speed가 속도 값이 아니면, cfsetospeed는 -1을 반환한다.

함수 : int cfsetispeed (struct termios *termios_p, speed_t speed)

이 함수는 입력 속도로 *termios`p에 speed를 저장한다. 보통의 반환 값은 0이고 에러가 발생하면 -1을 반환한다. 만일 speed가 속도 값이 아니라면, cfsetopeeed는 -1을 반환한다.

함수 : int cfsetspeed (struct termios *termios_p, speed_t speed)

이 함수는 입력과 출력의 속도 둘을 위해 *termios`p로 speed를 저장한다. 보통 반환 값은 0이고, 에러가 발생하면 -1을 반환한다. 만일 speed가 속도 값이 아니라면, cfsetspeed는 -1을 반환한다. 이 함수는 4. 4 BSD 확장이다.

데이터타입: speed_t

speed_type은 unsigned integer형으로 라인의 속도를 나타내기 위해 사용된다.

cfsetospeed 와 cfsetispeed 함수는 그 시스템이 간단히 취급할 수 없는 속도 값일 경우에만 에러를 표시한다. 만일 당신이 기본적으로 받아들일 수 있는 속도 값을 지정하면, 이 함수는 성공할 것이다. 그러나 그들은 어떤 특별한 하드웨어가 정해진 속도를 실제로 지원할 수 있는지를 체크할 수 없고, 실제로 그들은 당신이 속도를 설정하려 계획하는 디바이스를 알지 못한다. 만일 당신이 처리될 수 없는 값으로 특별한 디바이스의 속도를 설정하려고 tcsetattr을 사용하면, tcsetattr은 -1을 반환한다.

 
이식성 노트: GNU 라이브러리에서, 함수들은 입력과 출력으로 초(second)당 비트로 계산된 속도를 받아들인다. 다른 라이브러리들은 속도를 특별한 코드로 지정해준다. POSIX. 1과 이식성을 위해서는, 당신은 속도를 나타내기 위한 다음의 심볼들 중의 하나를 사용해야만 한다; 그들의 정확한 숫자적 값들은 시스템_의존적이지만, 각 이름은 정해진 의미를 갖고 있다. B110은 110 bps를 위한 것이고, B300은 300 bps를 위한 것이고. . 등등. . 이것은 속도를 나타내기 위한 다른 방법들과 전혀 이식성이 없지만, 그들은 특별한 직렬 라인을 지원할 수 있는 속도이다.

B0 B50 B75 B110 B134 B150 B200

B300 B600 B1200 B1800 B2400 B4800

B9600 B19200 B38400

BSD는 유사이름으로 두 개의 부가적 속도 심볼들은 정의한다. EXTA 는 B19200과 같고, EXTB는 B38400과 같다. 이들 유사어는 오래된 것이다.

함수 : int cfmakeraw (struct termios *termios_p)

이 함수는 BSD에서 전통적으로 원래의 모드"로 불려지는 모드로 *termios`p를 설정하는 쉬운 방법을 제공한다. 그것은 정확히 이런 일을 한다.
termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP
|INLCR|IGNCR|ICRNL|IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
termios_p->c_cflag &= ~(CSIZE|PARENB);
termios_p->c_cflag |= CS8;

 

12. 4. 9 특별 문자들

정규입력에서, 터미널 구동기는 다양한 제어 함수들을 수행하는 특별문자들의 수를 인식한다. 이들은 편집입력을 위한 ERASE 문자(보통은 DEL)와 그리고 다른 편집문자들이 포함된다. SIGINT 신호를 보내기 위한 INTR 문자( 보통 C -c)와 다른 신호-발생 문자들은, 다른 정규입력이나 비정규 입력 모드에서 유용할 것이다. 모든 이들 문자들은 이 절에 설명되어 있다.

특별한 문자들은 구조체 struct termios의 c_cc 멤버에 정해져있다. 이 멤버는 배열이다; 각 요소는 특별한 규칙을 가진 문자를 정한다. 각 요소는 그 요소의 인덱스를 위한 심볼 상수를 갖는다_예를 들어 INTR은 INTR 문자를 정하는 요소의 인덱스이다, 그래서 INTR 문자로 '='을 정하여 termios. c_cc[INTR]에 '='을 저장한다.

다른 시스템에서, 당신은 _POSIX_VDISABLE를 정하여 특별 문자 함수들을 불가능하게 할 수 있다. 이 값은 어느 가능한 문자 코드와 같지 않다. 27. 2절 [Options for Files] 를 참고로, 운영체제가 _POSIX_VDISABLE를 당신에게 지원하는지의 여부를 어떻게 알 수 있는지를 보아라.

 

12. 4. 9. 1 입력 편집을 위한 문자들

이들 특별 문자들은 오직 정규(canonical) 입력 모드에서만 작동된다. 12. 3절 [Canonical or Not] 참조.

매크로 : int VEOF

이것은 특별 제어 문자 배열에 있는 EOF 문자를 위한 첨자(subscript)이다. termios. c_cc[VEOF] 는 그 문자 자체를 저장하고 있다. EOF 문자는 오직 정규입력 모드에서만 인식된다. 그 문자는 새줄문자와 같은 방법으로 라인의 종료자(terminator)로서 동작하지만, 만일 EOF 문자가 라인의 처음에 존재된다면, 0바이트를 반환하여, 파일의 끝임을 지적한다. EOF 문자 그 자체는 버려진다. 보통, EOF 문자는 C-d 이다.

매크로 : int VEOL

이것은 특별 제어 문자배열에 있는 EOL 문자를 위한 첨자이다. termios. c_cc[VEOL] 은 문자 그 자체를 저장하고 있다. EOL 문자는 오직 정규입력 모드에서만 인식된다. 그것은 새줄문자처럼 라인 종료자(terminator) 로서 동작한다. 입력 라인에서 마지막 문자로 읽혀진 EOL문자는 버려진다. 한 라인의 끝인 RET을 만들기 위해서 EOL 문자를 사용할 필요가 없다. 단지 ICRNL 플래그를 설정하라. 실제로, 이것이 디폴트 상황이다.

매크로 : int VERASE

이것은 특별 제어문자 배열에 있는 ERASE문자를 위한 첨자이다. termios. c_cc[VERASE] 는 그 문자 자체를 저장한다. ERASE문자는 오직 정규입력 모드에서만 인식된다. 사용자가 erase 문자를 입력할 때, 전에 입력된 문자가 버려진다. ( 만일 그 터미널이 다중 바이트 (multibyte)문자열을 발생시킨다면, 이것은 입력에서 버려진 것이 한 바이트보다 더 많을 수도 있다. ) 이것은 텍스트의 현재 라인보다 앞의 것을 지울 수 없다. ERASE 문자 그 자체는 버려진다. 보통 ERASE문자는 DEL 이다.

매크로 : int VKILL

이것은 특별 제어문자 배열에 있는 KILL 문자를 위한 첨자이다. termios. c_cc[VKILL] 은 문자 그 자체를 저장하고 있다. KILL 문자는 오직 정규입력 모드에서만 인식된다. 사용자가 kill 문자를 입력할 때, 입력의 현재라인의 전체의 내용이 버려진다. kill 문자도 버려진다. KILL 문자는 보통 C-u 이다.

 

12. 4. 9. 2 편집 문자의 BSD 확장

이들 특별 문자들은 오직 정규입력 모드에서만 동작한다. 12. 3절 [Canonical of Not] 참조. 그들은 BSD 확장이다; GNU 라이브러리는 그들을 요청하면, 어느 시스템 상에서든지 그 심볼들을 사용하게 하지만, 그 문자들은 BSD 시스템을 제외하고는 아무 데서도 동작하지 않을 것이다.

매크로 : int VEOL2

이것은 특별 제어문자 배열에 있는 EOL2 문자를 위한 첨자이다. termios. c_cc[VEOL2] 는 문자 그 자체를 저장한다. EOL2문자는 EOL문자처럼 동작하지만( 위를 보라 ) 다른 문자가 될 수 있다. 그래서, 당신이 입력 라인을 끝내기 위해서 두 개의 문자들을 정할 수 있지만, 그들 중 하나를 위해서 EOL 문자를 설정하고 다른 것을 위해서 EOL2 문자를 설정하라.

매크로 : int VWERASE

이것은 특별 제어문자 배열에 있는 WERASE 문자를 위한 첨자이다. termios. c_cc[VWERSE] 는 문자 그 자체를 저장한다. WERASE 문자는 오직 정규입력 모드에서만 동작한다. 그것은 이전 입력의 전체 단어를 지운다.

매크로 : int VREPRINT

이것은 특별 제어문자 배열에 있는 REPRINT문자를 위한 첨자이다. termios. c_cc[BREPRINT]는 문자 그 자체를 저장한다. REPRINT 문자는 오직 정규입력 모드에서만 인식된다. 그것은 현재의 입력라인을 다시 프린트한다.

매크로 : int VLNEXT

이것은 특별 제어문자 배열에 있는 LNEXT 문자를 위한 첨자이다. termios. c_cc[VLNEXT]는 문자 그 자체를 저장한다. LNEXT 문자는 오직 IEXTEN이 설정되었을 때만 인식된다. 사용자가 입력한 다음문자의 편집을 불가능하게 한다. 이것은 Emacs에서 C-q 와 유사하다. "LNEXT"는 "literal next"를 나타낸다. LNEXT문자는 보통 C-v 이다.

 

12. 4. 9. 3 신호를 발생시키는 문자들

이들 특별문자들은 정규모드나 비정규모드를 상관하지 않고 동작하지만, ISIG 플래그가 설정되었을 때만 동작한다. ( 12. 4. 7절 [Local Modes] 참조. )

매크로 : int VINTR

이것은 특별 제어문자 배열에 있는 INTR 문자를 위한 첨자이다. termios. c_cc[VINTR] 은 문자 그 자체를 저장한다. INTR(interrupt)문자는 터미널과 연관된 전면 작업에 있는 모든 프로세스를 위해 SIGINT 신호를 발생시킨다. INTR 문자 그 자체는 버려진다. 21장 [Signal Handling] 를 참조로, 신호에 대한 정보를 보아라. 특별히, INTR 문자는 C-c 이다.

매크로 : int VQUIT

이것은 특별 제어문자 배열에 있는 QUIT문자를 위한 첨자이다. termios. c_cc[VQUIT] 는 문자 그 자체를 저장한다. QUIT 문자는 터미널과 연관된 전면작업에 있는 모든 프로세스를 위한 SIGQUIT 신호를 발생시킨다. QUIT 문자 그 자체는 버려진다. 21장[Signal Handling] 에서 신호에 대한 더 많은 정보를 참조하라. 특별히, QUIT 문자는 C-\이다.

매크로 : int VSUSP

이것은 특별 제어문자 배열에 있는 SUSP 문자를 위한 첨자이다. termios. c_cc[VSUSP]는 문자 그 자체를 저장한다. SUSP(suspend) 문자는 작업제어를 지원하는 동작에서만 인식된다( 24장 [Job Control] 참조). 그것은 터미널과 연관된 전면 작업에 있는 모든 프로세스들에게 보내기 위한 SIGTSTP 신호를 발생시킨다. SUSP 문자 그 자체는 버려진다. 신호에 대한 자세한 정보는 21장 [Signal Handling] 를 참조하라. 몇몇 응용프로그램에서는 SUSP 문자에 대한 해석을 불가능하게 한다. 만일 당신의 프로그램이 그렇게 한다면, 사용자가 그 작업을 멈출 수 있게 하기 위한 다른 메커니즘을 제공해야 할 것이다. 사용자가 이 메커니즘을 불렀을 때, 프로그램은 단지 그 프로세스 자신이 아닌, 프로세스의 프로세스그룹에게 SIGTSTP 신호를 보낸다. 21. 6. 2절 [Signaling Another Process] 참조.

매크로 : int VDSUSP

이것은 특별 제어문자 배열에 있는 DSUSP 문자를 위한 첨자이다. termios. c_cc[VDSUSP] 는 문자 그 자체를 저장한다. DSUSP(suspend) 문자는 작업 제어를 지원한 는 동작에서만 인식된다(24장 [Job Control] 참조). 그것은 SUSP 문자처럼 SIGTSTP 신호를 보내지만, 프로그램이 입력으로 그것을 읽으려 시도하는, 잘못된 행동을 취할 때 발생한다. 작업제어를 지원하는 모든 시스템에서 DSUSP가 지원되는 것이 아니라 오직 BSD 시스템에서만 지원된다. 신호에 대한 자세한 정보는 21장 [Signal Handling] 를 참조하라. 특별히, DSUSP 문자는 C-y 이다.

 

12. 4. 9. 4 흐름 제어를 위한 특별 문자들

이들 특별 문자들은 정규입력 모드나 비정규입력모드에 상관없이 동작되지만, 그들의 사용은 IXON과 IXOFF 플래그에 의해 제어된다. ( 12. 4. 4절 [Input Modes] 참조. )

매크로 : int VSTART

이것은 특별 제어문자 배열에서 START 문자를 위한 첨자이다. termios. c_cc[VSTART] 는 문자 그 자체를 저장한다. START 문자는 IXON 과 IXOFF 입력 모드를 지원하기 위해서 사용된다. 만일 IXON이 설정되고, START 문자를 받으면 보류된 출력을 다시 시작한다. 이때 START 문자는 버려진다. 만일 IXOFF 가 설정되면, 시스템은 터미널에 START 문자를 전송할 것이다. START 문자의 보통의 값은 C-q 이다. 당신이 무엇으로 정하든 지에 상관없이 하드웨어에 C-q로 정해져 있다면 당신은 이 값을 변경할 수 없을 것이다.

매크로 : int VSTOP

이것은 특별 제어문자 배열에 있는 STOP 문자를 위한 첨자이다. termios. c_cc[VSTOP] 는 문자 그 자체를 저장한다. STOP 문자는 IXON 과 IXOFF 입력 모드를 지워하기 위해 사용된다. 만일 IXON이 설정되고, STOP 문자를 받으면 출력을 보류시킨다; 이때 STOP 문자 자체는 버려진다. 만일 IXOFF 가 설정되면, 시스템은 입력큐에서 오버플로우가 발생하는 것을 방지하기 위해 터미널에 STOP 문자를 전송할 것이다. STOP를 위해서 보통 사용되는 값은 C-s 이다. 당신은 당신이 무엇으로 이 값을 변경하든 지에 상관없이 하드웨어에서 C-s로 고정되어 있다면, 이 값을 변경할 수 없다.

 

12. 4. 9. 5 다른 특별 문자들

이곳에서는 BSD 시스템에서 의미가 있는 두 개의 부가적 특별 문자들을 설명한다.

매크로 : int VDISCARD

이것은 특별 제어문자 배열에 있는 DISCARD 문자를 위한 첨자이다. termios. c_cc[VDISCARD] 는 문자 그 자체를 저장한다. DISCART문자는 IEXTEN이 설정되었을 때만 인식된다. 그 영향은 discard-output 플래그를 토글하게된다. 이 플래그가 설정되면, 모든 프로그램 출력은 버려진다. 플래그 설정은 또한 출력 버퍼안에 현재 존재하는 모든 출력을 버린다.

매크로 : int VSTATUS

이것은 특별 제어문자 배열에 있는 STATUS 문자를 위한 첨자이다. termios. c_cc[VSTATUS] 는 문자 그 자체를 저장한다. STATUS 문자의 영향은 현재 프로세스가 어떻게 동작하고 있는지에 대한 상황 메시지를 출력하기 위한 것이다. STATUS 문자는 오직 정규 입력 모드에서만 인식된다.

 

12. 4. 10 비정규입력

비정규입력 모드에서, ERASE 와 KILL 과 같은 특별 편집 문자들을 무시된다. 입력 편집을 위해 사용자에게 부여된 시스템 도구들이 비정규입력 모드에서는 불가능하게 된다. 그래서 모든 입력 문자들( 만약 그들이 신호나 흐름제어 목적을 가진 것이 아니라면)은 응용 프로그램에 정확히 친 대로(typed) 인식된다. 응용 프로그램에서 사용자에게 입력을 편집하는 방법을 제공하는 것이 좋다.

비정규입력 모드는 유용한 입력이 있을 때까지 기다릴 것인지와 얼마나 기다릴 것인지를 제어하기 위한 MIN 과 TIME 이라 불리는 파라미터가 있다. 당신은 유용한 입력이 있을 때, 또는 없을 때, 즉시 반환되도록 해서 무작정 기다리는 것을 피하는데도 그들을 사용할 수 있다.

MIN과 TIME은 구조체 struct termios의 멤버인 c_cc 배열의 요소로서 저장되어진다. 이 배열의 각 요소는 특별한 규칙을 가지고 있고, 각 요소는 그 요소의 인덱스로 대표되는 심볼 상수를 가지고 있다. VMIN 과 VMAX는 MIN과 TIME 슬롯의 배열의 인덱스들을 위한 이름들이다.

매크로 : int VMIN

이것은 c_cc 배열안의 MIN 슬롯을 위한 첨자이다. 그래서 termios. c_cc[VMIN]은 그 값 자체이다. MIN 슬롯은 비정규입력 모드에서만 유용하다; read가 반환되기 전에 입력큐에서 받아들여야만 하는 유용한 바이트의 최소 개수를 정하는데 사용된다.

매크로 : int VTIME

이것은 c_cc 배열에 있는 TIME 슬롯을 위한 첨자이다. 그래서, termios. c_cc[VTIME] 는 그 값 자체이다. TIME 슬롯은 비정규입력 모드에서만 유용하다; 그것은 0. 1초의 단위로 반환하기 전에 입력을 얼마나 기다릴 것인지를 정한다. MIN 과 TIME값은 read가 반환할 때를 위한 표준을 정하는데 영향을 미친다; 그들의 정확한 의미는 그들이 가진 값에 따른다(0이냐 0이 아니냐. . ) 4개의 가능한 경우가 있다.

MIN 과 TIME 둘다 영일 때. . .

이 경우, read는 큐에 요청된 개수를 넘어서는, 유용한 입력이 있을 때 즉시 반환한다. 만일 아무런 입력이 없어도 즉시 반환하는데, 이때 read는 0의 값을 반환한다.

MIN 은 0이지만 TIME은 0이 아닐 때.

이 경우, read는 유용한 입력이 될 때까지 TIME 시간동안 기다린다; 단 한 개의 바이트도 요청한 read를 만족시키기에 충분하고, read는 반환한다. 그것이 반환할 때 요청된 개수의 유용한 문자를 반환한다. 만일 시간이 다할 때까지 유용한 입력이 없으면 read는 0을 반환한다.

TIME 이 0이지만 MIN은 0이 아닐 때.

이 경우, read는 적어도 큐에 유용한 입력이 MIN 바이트가 될 때까지 기다린다. 그 시간동안, read는 요청된 개수의 유용한 문자들을 반환한다. read는 만일 큐에 MIN보다 더 많은 문자가 발생했다면 MIN 문자보다 더 많은 문자를 반환할 수 있다.

TIME 과 MIN 둘다 영이 아닐 때.

이 경우, TIME은 만일 입력이 도착하면, 그 첫 번째 도착한 입력부터 얼마동안 기다릴 것인가를 정한다. read는 MIN 바이트의 입력이 도착하거나, 또는 TIME이 더 이상 아무런 입력 없이 경과되었을 때까지 기다림을 유지한다. read는 TIME이 첫 번째 입력이 도착하기 전에 경과되면 입력 없이 반환할 수 있다. read는 MIN 보다 더 많은 입력이 큐안에 발생하면 그것을 반환할 수 있다 만일 MIN이 50이고 당신이 단지 10 바이트만 읽기를 요청하면 무슨 일이 발생할 것인가?
 
보통, read는 버퍼에 50바이트가 찰 때까지 기다린다(또는 더 일반적으로는, 위에 설명된 기다림의 상황이 만족된다. ), 그리고 나서, 그들 중 10개를 읽고, 나머지 버퍼에 있는 40개는 read의 연속적 호출에서 사용하기 위해서 운영체제 안에 남겨둔다.
 
이식성 노트: 어떤 시스템에서, MIN 과 TIME 슬롯은 실제로 EOF 와 EOL 슬롯과 같다. MIN 과 TIME은 비정규 입력에서만 사용되고, EOF와 EOL은 정규입력에서만 사용되기 때문에 심각한 문제는 없지만, 완전한 것은 아니다. GNU 라이브러리는 이렇게 사용하기 위해서 슬롯들을 분리해서 할당한다.


12. 5 라인 제어 함수들

이들 함수들은 터미널 디바이스 상에서 갖가지 제어 동작을 수행한다. 터미널 억세스에 관하여, 그들은 출력을 하는 것처럼 취급된다: 만일 그들 함수중 어떤 것이 터미널을 제어중인 배경 프로세스에서 사용된다면, 보통, 프로세스 그룹의 모든 프로세스들은 SIGTTOU 신호를 받는다. 예외적으로 호출한 프로세스 자신이 무시되거나, SIGTTOU 신호에 의해 블록되어 있다면, 그 경우 명령은 수행되고 아무런 신호를 받지 않는다. 24장 [JobControl] 참조.

함수 : int tcsendbreak( int filedes, int duration)

이 함수는 파일 기술자 filedes와 연관된 터미널에 0 비트의 스트림을 전송함으로써 멈춤(break) 상황을 발생시킨다. 멈춤의 존속시간은 duration인수에 의해 제어된다. 만일 duration 이 0이면, 존속시간은 0. 25 와 0. 5초 사이이다. 0이 아닌 값의 의미는 운영체제에 의존된다. 이 함수는 만일 그 터미널이 비동기적 직렬 데이터 포트가 아니면 아무 일도 하지 않는다.
 
반환 값은 보통 0이고, 에러가 발생하면 -1을 반환한다. 다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

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

ENOTTY : filedes 가 터미널 디바이스와 연관이 없다.

함수 : int tcdrain (int filedes)

tcdrain 함수는 큐에 저장된 모든 출력이 터미널 filedes에 모두 전송되어 질 때까지 기다린다.
 
반환 값은 보통 0이고, 에러가 발생하면 -1을 반환한다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

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

ENOTTY : filedes는 터미널 디바이스와 연관이 없다.

EINTR : 그 명령은 신호에 의해 인터럽트 되어졌다.

21. 5절 [Interrupter Primitives] 참조.

함수 : int tcflush (int filedes, int queue)

tcflush 함수는 터미널 파일 filedes와 연관된 입력 그리고/또는 출력큐를 소거하기 위해 사용된다. queue 인수는 소거할 큐를 정하고, 다음 값들중 하나를 사용할 수 있다.

TCIFLUSH : 받았지만, 아직 읽지않은 입력 데이터를 소거하라

TCOFLUSH  : 쓰여졌지만, 아직 전송되지 않은 출력데이터를 소거하라.

TCIOFLUSH

큐에 저장된 입력과 출력을 모두 소거하라. 반환 값은 보통 0이고 에러가 발생하면 -1을 반환한다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

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

ENOTTY : filedes는 터미널 디바이스와 연관이 없다.

EINVAL

적당하지 못한 값이 queue인수로써 공급되었다. 이 함수의 이름이 tcflush라고 지어진 것은 유감스러운데, 왜냐하면 한정 "flush"는 보통 모든 출력이 전송되고, 다른 명령을 사용하기 전에 혼동될 입력이나 출력을 버리는데 사용된다. 유감스럽게도, tcflush는 POSIX로부터 유래됐고, 우리는 그것을 변경할 수 없다.

함수 : int tcflow (int filedes, int action)

tcflow 함수는 filedes로 정해진 터미널 파일에서 XON/XOFF 흐름제어에 해당하는 명령을 수행하기 위해 사용된다. action인수는 무슨 명령을 수행할 것인지를 정하고, 다음 값들중 하나를 가질 수 있다.

TCOOFF 출력의 전송을 중단하라.

TCOON 출력의 전송을 다시 시작하라.

TCIOFF STOP 문자를 전송하라.

TCION START 문자를 전송하라.

STOP 와 START에 대한 자세한 정보를 12. 4. 9절 [Special Characters] 를 참조하라.

반환 값은 보통 0이고 에러가 발생하면 -1이 반환된다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

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

ENOTTY filedes 가 터미널 디바이스와 연관이 없다.

EINVAL 적당하지 못한 값이 action인수로 주어졌다.


12. 6 비정규 모드의 예

이곳의 예는 비정규입력모드에서 반향 없이 단일 문자들을 읽기 위해서 터미널 디바이스를 어떻게 맞출 것인지를 보여주고 있다.

#include <unistd. h>
#include <stdio. h>
#include <stdlib. h>
#include <termios. h>
/* 원래의 터미널 속성들을 기억하기 위해서 이 변수를 사용하라 */
 
struct termios saved_attributes;
 
void
reset_input_mode (void)
{
tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes);
}
 
void
set_input_mode (void)
{
struct termios tattr;
char *name;
 
/* stdin이 터미널인지 확인하라 */
if (!isatty (STDIN_FILENO)) {
fprintf (stderr, "Not a terminal. \n");
exit (EXIT_FAILURE);
}
 
/* 그들을 나중에 다시 저장 할 수 있도록 터미널 속성들을 저장하라. */
tcgetattr (STDIN_FILENO, &saved_attributes);
atexit (reset_input_mode);
 
/* 재미있는(?) 터미널 모드를 설정하라. */
tcgetattr (STDIN_FILENO, &tattr);
tattr. c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
tattr. c_cc[VMIN] = 1;
tattr. c_cc[VTIME] = 0;
tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr);
}
 
int
main (void)
{
char c;
 
set_input_mode ();
 
while (1) {
read (STDIN_FILENO, &c, 1);
if (c == '\004') /* C-d */
break;
else
putchar (c);
}
 
return EXIT_SUCCESS;
}

이 프로그램은 신호와 함께 빠져나가거나 종료되기 전에 원래의 터미널 모드를 재 저장하도록 주의를 기울여야 한다. 이것을 확실하게 하기 위해서는 atexit 함수를( 22. 3. 3절 [Cleanups on Exit] 참조. ) 사용하라. 쉘은 한 프로세스가 멈추거나 지속될 때 터미널 모드의 재설정에 조심한다. 24장 [Job Control] 참조. 그러나 어떤 쉘들은 실제로 이것을 하지 않는다, 그래서 당신은 터미널 모드를 재설정하는 작업 제어 신호를 위한 핸들러를 만들어야 할 것이다. 위의 예는 그렇게 한다.


목차 이전 : 11. 소켓 다음 : 13. 수학함수

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

14. 저수준 연산 함수들  (0) 2007.12.22
13. 수학 함수  (0) 2007.12.22
12. Low-Level Terminal Interface  (0) 2007.12.22
11. Sockets  (0) 2007.12.22
10. Pipes and FIFO  (0) 2007.12.22
9. 파일시스템 인터페이스  (0) 2007.12.22
Comment 0 Trackback 0
Top

prev 1 2 3 4 next