'분류 전체보기'에 해당되는 글 143건

  1. 2008.12.23 Thread and Signal
  2. 2008.12.23 pthread_mutex_lock
  3. 2008.12.23 Pthread API Reference
  4. 2008.12.23 아스키코드표
  5. 2008.12.23 파일 정보의 획득 stat & fstat
  6. 2008.12.23 Red-black tree 1
  7. 2008.12.23 [UNIX] 문자, 문자열 관련 함수
  8. 2008.12.23 [UNIX] getopt() 함수
  9. 2008.12.22 SNMP
  10. 2008.12.15 초간단 AVL Tree
2008. 12. 23. 14:00

JOINC.co.kr

쓰레드와 시그널

Date: 2003/10/27

Topic: 시스템 프로그램

윤상배: dreamyun@yahoo.co.kr

 

 

그렇잖아도 애매 모호한 쓰레드에 헷갈리는 시그널을 사용하고자 하면 여러 가지 애로 사항이 꽃피게 된다. 각 쓰레드별로 시그널이 전달되거나 전달되지 않도록 설정할 수 있어야 하기 때문인데, 개념적으로는 간단하지만 막상 적용하려면 그 과정이 머리에 그려지지 않기 때문이다.

 

1절. 쓰레드에서의 시그널 사용

 

쓰레드에서의 시그널 사용은 시그널에 대한 기본적인 이해만 가지고 있다면 약간의 응용으로 충분히 해결할 수 있는 문제이긴 하지만 범 유닉스적으로 응용하고자 한다면(특히 리눅스가 포함된다면) 운영체제 간 신경써줘야 할 문제가 있다. 이번 장에서는 쓰레드에서의 시그널을 이용하는 방법과 운영체제가 다름으로 인해 발생할 수 있는 문제들에 대해서 알아보도록 하겠다.

 

1.1절. 시그널을 특정 쓰레드로 보내기

 

쓰레드에서 시그널은 서로 공유된다는 걸 알고 있을 것이다. 문제는 공유된다는 점인데 만약 프로세스에 시그널을 보낼 경우 해당 프로세스에서 생성된 모든 쓰레드에 시그널이 전달이 되게 된다. 이것은 우리가 원하는게 아니다.

 

우리가 원하는 것은 특정 쓰레드에서만 시그널을 받도록 하는 것이다. 이러한 작업을 위해서 우리는 시그널 마스크를 사용한다. 시그널 마스크는 말 그대로 특정 시그널에 대해서 마스크를 씌우는 것으로 해당 쓰레드에서 특정 시그널에 대해서 마스크를 씌우면 마스킹된 시그널은 해당 쓰레드로 전달되지 않는다. 이 시그널을 받기를 원하는 쓰레드에서는 이 시그널에 대한 마스크를 제거시킨다. 그러면 블록되어 있는 시그널은 마스크가 제거된 쓰레드로 전달 될 것이다. 일종의 필터기다.

 

그림 1. 시그널 마스크의 작동원리

 

위의 그림은 시그널 마스크의 작동 원리를 보여준다. 메인 쓰레드에서는 SIGINT와 SIGUSR2에 대해서 시그널 마스크를 설치한다. 그리고 쓰레드 1에서는 SIGINT에 대한 마스크를 제거하고, 쓰레드 2에서는 SIGUSR2에 대한 마스크를 제거한다. 이렇게 되면 SIGINT가 메인 쓰레드에 도착했을 때 마스크 때문에 메인 쓰레드에는 도착하지 못하고 쓰레드 1로 전달될 것이다. SIGUSR2가 도착했을 경우 메인 쓰레드와 쓰레드 1에서는 마스크 때문에 전달되지 못하고 쓰레드 2로 시그널이 전달된다. 1.1.1절에서는 위의 작동 원리대로 구현된 예제 코드를 다루고 있다.

 

이러한 쓰레드별 시그널 마스킹을 위해서 pthread는 pthread_sigmask(3)라는 함수를 제공한다.

#include <pthread.h>
#include <signal.h>

 

int pthread_sigmask(int how, const sigset_t *newmask, sigset_t *oldmask);

 

이 함수는 현재 쓰레드에 시그널 newmask와 how를 이용해서 시그널 마스크를 만든다. how는 SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK 중 하나를 선택할 수 있다. SIG_BLOCK은 현재 설정된 시그널 마스크에 newmask를 추가하며 SIG_UNBLOCK은 현재 설정된 시그널 마스크에서 newmask를 제거하고 SIG_SETMASK는 newmask로 현재 시그널 마스크를 설정한다.

 

1.1.1절. 간단 예제

 

그럼 pthread_mask(3)를 이용한 간단한 예제를 만들어 보도록 하겠다. 코드는 여러분이 시그널과 쓰레드에 관한 최소한의 지식을 가지고 있다는 가정하에 작성될 것이며, 설명은 주석으로 대신하도록 하겠다.

 

예제 : th_signal.c

위 프로그램을 실행시킨 뒤 kill 명령으로 SIGINT와 SIGUSR2 시그널을 PID로 보내보면 해당 쓰레드로 시그널이 전달되고 시그널 핸들러가 실행되는 걸 확인할 수 있을 것이다.

 

1.2절. 쓰레드 간 시그널 전송

 

외부의 다른 프로세스에서 시그널을 발생시키는 것 외에도 같은 프로세스에서 작동하는 쓰레드 간에 시그널을 전송해야 하는 경우도 생길 것이다.

 

이러한 쓰레드 간 시그널 전송은 여러 가지 목적으로 사용할 수 있다. 일정 시간마다 특정 쓰레드에 시그널을 전송하므로써 쓰레드를 깨워서 코드를 실행시키게 한다거나 네트워크 애플리케이션에서 write, read에 타임아웃을 검사하는 용도로도 사용 가능하다.

 

네트워크 애플리케이션에서 스레드 간 시그널 전달을 통해 타임아웃을 검사한다는 생각은 좀 생소할 수도 있을 것 같다. 보통은 select나 alarm을 사용할 건데, 멀티 쓰레드 프로그램의 경우 alarm(2)의 사용은 사실상 어렵다고 볼 수 있다. 여러 개의 쓰레드에서 alarm(2)를 사용할 경우 단지 하나의 alarm(마지막 alarm 값)만이 등록되어서 사용할 수 있기 때문이다. 그렇다면 select를 사용해야 할 건데, select 대신에 전용의 시그널을 발생하는 쓰레드를 이용해서 사용할 수 있다.

 

read(2)를 예로 들어서 설명해 보자 read(2)를 하기 전에 특정 (전역) 값을 0으로 세팅하고 read를 수행한 후 1로 값을 변경하도록 한다. 그리고 타임아웃 체크를 위한 쓰레드에서는 타임아웃 시간 간격으로(sleep(2)를 이용하면 된다) 이 값을 검사한다. 만약 값이 0으로 세팅되어 있는 걸 확인 했는데, 다음 시간이 돌아온 뒤에도 이 값이 0이라면 read 영역에서 타임아웃이 발생했다고 판단 할 수 있을 것이다. 그러면 타임아웃이 발생한 쓰레드에 시그널을 전송하도록 한다. 쓰레드에 시그널을 전송하면 인터럽트가 발생하고 read에서 빠져나오게 된다.

if (read(..) < 0)
{
        // 만약 인터럽트로 인하여 빠져나온 거라면..
        if (errno == EINTR)
        {
                ...
        }
}

시그널 발생시 인터럽트가 전달되게 하려면 약간의 부가적인 작업이 필요한데, 이것은 소켓 타임아웃을 참고하기 바란다.

 

1.2.1절. 다른 쓰레드로 시그널 전송

 

이러한 쓰레드 간 시그널 전송을 위해서 pthread_kill(3)가 제공된다.

#include <pthread.h>
#include <signal.h>


int pthread_kill(pthread_t thread, int signo);

 

첫 번째 인자 thread는 시그널을 전달받을 쓰레드의 식별자이고 signo은 전달하고자 하는 시그널 번호이다. 보내는 쪽은 pthread_kill(3)을 이용해서 비교적 간단하게 구현이 가능하다.

 

1.2.2절. 시그널받기

 

시그널을 받는 쓰레드의 경우 동기와 비동기 두 가지 방식을 통해서 받을 수 있다. 동기 방식으로 받을 경우는 sigwait(3) 함수를 이용해서 시그널이 전달될 때까지 블록되면서 기다린다.

#include <pthread.h>
#include <signal.h>

 

int sigwait(const sigset_t *set, int *sig);

 

이 함수는 시그널 set에 설정된 시그널 중 하나가 전달될 때까지 호출된 영역에서 대기한다. 시그널을 받았다면 리턴되고 전달 받은 시그널 번호는 sig를 통해서 넘어온다. 시그널을 기다린다는 특징을 이용해서 쓰레드 간 동기화를 위한 목적으로도 유용하게 사용할 수 있을 것이다.

 

두 번째는 비동기적인 방식으로 코드 실행 중에 시그널이 전달되면 인터럽트가 걸리고 시그널 핸들러가 수행되는 방식이다. 일반적인 시그널 사용 방식과 동일하다.

 

1.2.3절. 예제

 

sigwait(3)를 통해서 동기적으로 기다리는 것은 구현이 간단하므로 따로 다루지 않고 시그널 핸들러를 등록해서 비동기적으로 시그널을 기다리는 코드를 구현해 보도록 하겠다. 1.1.1절의 코드를 약간 수정했다.

 

예제 : thtoth_sig.c

위의 코드의 경우 시그널을 받을 쓰레드를 명시해줄 수 있으므로 시그널 마스크 등을 설치할 필요가 없다. SIGINT가 원하는 쓰레드로 정확하게 전달되는 걸 확인할 수 있을 것이다.

 

1.3절. 운영체제별 차이점

 

쓰레드의 작동 방식은 운영체제별로 많은 차이를 보여줄 수 있으며, 차이점에 유의해서 프로그램을 작성해야 한다. 여기에서는 솔라리스와 리눅스를 비교해서 설명하도록 하겠다.

 

지금까지의 쓰레드와 시그널에 대해서 다루었던 내용은 솔라리스와 같이 하나의 프로세스에서 다중의 쓰레드를 관리하는 경우를 기준으로 했다. 그러나 리눅스의 경우 clone(2)를 통한 다중 프로세스 형태로 쓰레드가 생성된다. 때문에 ps를 이용해서 확인할 경우 다중 쓰레드 프로세스임에도 불구하고 각각의 PID를 가지는 프로세스로 쓰레드가 생성되는걸 확인 할 수 있다.

 

이런 특징 때문에 리눅스 시스템에서 외부 프로세스에서 시그널을 특정 쓰레드로 보낼 경우에는 메인 쓰레드가 아닌 해당 쓰레드의 PID를 명시해 주어야 한다.

 

This article comes from Joinc

http://www.joinc.co.kr


Posted by 두장
2008. 12. 23. 13:36

1장. pthread_mutex_lock(3)

차례
1.1절. 사용법
1.2절. 설명
1.3절. 반환값
1.4절. 에러
1.5절. 예제
1.6절. 참고문헌

뮤텍스 잠금을 얻거나 해제한다.


1.1절. 사용법

#include <pthrad.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
		


1.2절. 설명

mutex는 MUTual EXclusion(상호 배제)devide의 줄임말로 쓰레드간 공유하는 데이터 영역을 보호하기 위해서 사용한다. 데이터 영역의 보호는 critical section(임계 영역)을 만들고 임계 영역내에 단하나의 쓰레드만이 진입가능 하도록 하는 방식을 사용한다.

보통 이 임계영역에는 보호하고자 하는 데이터에 대한 접근/수정 루틴이 들어간다. 데이터에 대한 접근/수정 루틴에 오직 하나의 쓰레드만 접근 가능하게 되므로 결국 데이터를 보호할 수 있게 된다.

뮤텍스는 단지 2개의 가능한 행동만이 정의되어 있다. unlock와 lock이 그건데, lock는 임계영역은 진입하기 위한 요청, unlock는 임계영역을 빠져나오면서 다른 쓰레드에게 임계영역을 되돌려주기 위해서 사용한다. 만약 쓰레드가 임계영역이 진입하기 위해서 lock를 시도 했는데, 다른 쓰레드가 이미 임계영역에 진입했다면 해당 쓰레드가 unlock를 해서 임계영역을 빠져나오기 전까지 기다리게 된다.

mutex는 fast recursive의 2가지 종류가 지원된다. 이것은 lock을 얻은 쓰레드가 다시 lock를 얻을 수 있도록 할 것인지를 결정하기 위해서 사용한다. 기본적으로 mutex의 종류는 fast 상태로 시작된다. mutex 종류에 대한 자세한 내용은 pthread_mutexattr_init(3)을 참고하기 바란다.

pthread_mutex_t는 뮤텍스의 특징을 결정하기 위해서 사용한다.PTHREAD_MUTEX_INITIALIZER(fast mutex)와PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP(recursive mutexe), PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP(mutx 에러 체크용)의 3가지 상수가 준비되어 있다. 이중 하나를 선택하면 된다.

pthread_mutex_lock()는 (임계영역에 진입하기 위함)뮤텍스 잠금을 요청한다. 만약 뮤텍스의 최근 상태가 unlocked라면 쓰레드는 잠금을 얻고 임계영역에 진입하게 되고 리턴한다. 다른 쓰레드가 뮤텍스 잠금을 얻은 상태라면 잠금을 얻을 수 있을 때까지 기다리게 된다.

pthread_mutex_unlock()는 뮤텍스잠금을 되돌려준다. 만약 fast 뮤텍스라면 pthread_mutex_unlock()는 언제나 unlocked 상태를 되돌려준다. recursive 뮤텍스라면 잠겨있는 뮤텍스의 수를 감소시키고 이 수가 0이 된다면 뮤텍스잠금을 되돌려주게 된다.

pthread_mutex_destory()는 뮤텍스 객체를 삭제하고 자원을 되돌려준다. 더이상 사용하지 않는 뮤텍스는 반드시 이 함수를 이용해서 삭제하도록 하자. 리눅스에서 쓰레드는 뮤텍스 객체와 별개로 되어 있다. 그러므로 쓰레드가 종료되었다고 하더라도 뮤텍스 객체는 여전히 남아 있게 된다. 이 함수를 호출해야지만 뮤텍스 객체가 삭제 된다.


1.3절. 반환값

pthread_mutex_init()는 언제나 0을 리턴한다. 다른 뮤텍스 함수들은 성공했다면 0, 실패했다면 0이 아닌 수를 리턴하고 errno를 설정한다.


1.4절. 에러

pthread_mutex_lock()함수는 아래의 에러코드를 반환한다.

EINVAL

뮤텍스가 잘못 초기화 되었다.

EDEADLK

이미 잠금을 얻은 쓰레드가 다시 잠금을 요청할 때 (error checking 뮤텍스일 경우 사용할 수 있다)

pthread_mutex_trylock()함수는 아래의 에러코드를 반환한다.

EBUSY

뮤텍스가 잠겨 있어서 잠금을 얻을 수 없다.

EINVAL

뮤텍스가 잘못 초기화 되었다.


1.5절. 예제

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <iostream>

using namespace std;

void *ping(void *);
void *pong(void *);

pthread_mutex_t sync_mutex;
pthread_cond_t  sync_cond;

pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  gcond  = PTHREAD_COND_INITIALIZER;

int main()
{
    vector<void *(*)(void *)> thread_list;
    vector<pthread_t> tident(10); 
    int thresult;
    int status;
    int i;

    pthread_mutex_init(&sync_mutex, NULL);
    pthread_cond_init(&sync_cond, NULL);

    thread_list.push_back(pong);
    thread_list.push_back(ping);

    for(i = 0; i < thread_list.size(); i++ )
    {
        pthread_mutex_lock(&ync_mutex);
        if (pthread_create(&tident[i], NULL, thread_list[i], (void *)NULL) <0)
        {
            perror("error:");
            exit(0);
        }
        pthread_cond_wait(&sync_cond, &sync_mutex);
        pthread_mutex_unlock(&sync_mutex);
    }
    for (i = 0; i < tident.size(); i++)
    {
        pthread_join(tident[i], (void **)&status);
    }
}

void *ping(void *data)
{
    int i=0;
    pthread_mutex_lock(&sync_mutex);
    pthread_cond_signal(&sync_cond);
    pthread_mutex_unlock(&sync_mutex);
    while(1)
    {
        pthread_mutex_lock(&gmutex);
        printf("%d : ping\n", i);
        pthread_cond_signal(&gcond);
        pthread_cond_wait(&gcond, &gmutex);
        pthread_mutex_unlock(&gmutex);
        usleep(random()%100);
        i++;
    }
}

void *pong(void *data)
{
    int i = 0;
    pthread_mutex_lock(&sync_mutex);
    sleep(1);
    pthread_cond_signal(&sync_cond);
    pthread_mutex_unlock(&sync_mutex);
    while(1)
    {
        pthread_mutex_lock(&gmutex);
        pthread_cond_wait(&gcond, &gmutex);
        printf("%d : pong\n", i);
        pthread_cond_signal(&gcond);
        pthread_mutex_unlock(&gmutex);
        i++;
    }
}
		


Posted by 두장
article_Pthread_API_Reference위키 홈으로


Pthread API Reference

윤 상배

고친 과정
고침 0.9 2004년 6월 30일 12시
pthread 취소관련 api 추가
고침 0.8 2003년 10월 9일 12시
pthread 시그널 관련 api 추가

1. 소개

이 문서는 pthread 레퍼런스 문서이다. pthread 에서 제공하는 모든 함수의 레퍼런스를 제공하고 있지는 않지만, 자주 쓰일만한 대부분의 함수들은 정리되어 있음으로 참고할만한 가치가 있을것이다.

이 문서에 빠진 내용들은 계속 추가해 나갈 예정이다.


2. 기본 쓰레드 함수

주로 쓰레드 생성과 종료에 관련된 가장 기본적인 함수들이다.


2.1. pthread_create

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine)(void *), void *arg);
			
쓰레드 생성을 위해서 사용한다. 첫번째 아규먼트인 thread 는 쓰레드가 성공적으로 생성되었을때 생성된 쓰레드를 식별하기 위해서 사용되는 쓰레드 식별자이다. 두번째 아규먼트인 attr 은 쓰레드 특성을 지정하기 위해서 사용하며, 기본 쓰레드 특성을 이용하고자 할경우에 NULL 을 사용한다. 3번째 아규먼트인 start_routine는 분기시켜서 실행할 쓰레드 함수이며, 4번째 아규먼는인 arg는 쓰레드 함수의 인자이다.

성공적으로 생성될경우 0을 리턴한다.

예제 : pthread_create.cc

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

// 쓰레드 함수
void *t_function(void *data)
{
    int id;
    int i = 0;
    id = *((int *)data);

    while(1)
    {
        printf("%d : %d\n", id, i);
        i++;
        sleep(1);
    }
}

int main()
{
    pthread_t p_thread[2];
    int thr_id;
    int status;
    int a = 1;
    int b = 2;

    // 쓰레드 생성 아규먼트로 1 을 넘긴다.  
    thr_id = pthread_create(&p_thread[0], NULL, t_function, (void *)&a);
    if (thr_id < 0)
    {
        perror("thread create error : ");
        exit(0);
    }

    // 쓰레드 생성 아규먼트로 2 를 넘긴다. 
    thr_id = pthread_create(&p_thread[1], NULL, t_function, (void *)&b);
    if (thr_id < 0)
    {
        perror("thread create error : ");
        exit(0);
    }

    // 쓰레드 종료를 기다린다. 
    pthread_join(p_thread[0], (void **)&status);
    pthread_join(p_thread[1], (void **)&status);

    return 0;
}
			
실행된 쓰레드에 대해서는 pthread_join 등의 함수를 이용해서 쓰레드 종료때까지 기다려줘야 한다. ptherad_join 은 일종의 fork 의 wait 와 비슷하게 작동하며, 쓰레드자원을 해제 시켜준다.


2.2. pthread_join

#include <pthread.h>
int pthread_join(pthread_t th, void **thread_return);
			
첫번째 아규먼트 th는 기다릴(join)할 쓰레드 식별자이며, 두번째 아규먼트 thread_return은 쓰레드의 리턴(return) 값이다. thread_return 이 NULL 이 아닐경우 해다 포인터로 쓰레드 리턴 값을 받아올수 있다.

pthread_joinc.c

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

// 쓰레드 함수 
// 1초를 기다린후 아규먼트^2 을 리턴한다. 
void *t_function(void *data)
{
    int num = *((int *)data);
    printf("num %d\n", num);
    sleep(1);
    return (void *)(num*num);
}

int main()
{
    pthread_t p_thread;
    int thr_id;
    int status;
    int a = 100;

    thr_id = pthread_create(&p_thread, NULL, t_function, (void *)&a);
    if (thr_id < 0)
    {
        perror("thread create error : ");
        exit(0);
    }
    // 쓰레드 식별자 p_thread 가 종료되길 기다렸다가 
    // 종료리턴값을 가져온다. 
    pthread_join(p_thread, (void *)&status);
    printf("thread join : %d\n", status);

    return 0;
}
			


2.3. pthread_detach

int pthread_detach(pthread_t th);
			
detach 는 "떼어내다" 라는 뜻을 가지며 main 쓰레드에서 pthread_create 를 이용해 생성된 쓰레드를 분리시킨다. 이 함수는 식별번호th인 쓰레드를 detach 시키는데, detach 되었을경우 해당(detach 된) 쓰레드가 종료될경우 pthread_joinc 을 호출하지 않더라도 즉시 모든 자원이 해제(free) 된다.

여기에서는 pthread_create 호출후 detach 하는 방법을 설명하고 있는데, pthread_create 호출시에 쓰레드가 detach 되도록 할수도 있다. 이에 대한 내용은 pthread_attr_setdetachstate 를 다루면서 설명하도록 하겠다.

예제 : pthread_detach.c

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

// 쓰레드 함수
// 1초를 기다린후 아규먼트^2 을 리턴한다.
void *t_function(void *data)
{
    char a[100000];
    int num = *((int *)data);
	printf("Thread Start\n");
    sleep(5);
	printf("Thread end\n");
}

int main()
{
    pthread_t p_thread;
    int thr_id;
    int status;
    int a = 100;

	printf("Before Thread\n"); 
    thr_id = pthread_create(&p_thread, NULL, t_function, (void *)&a);
    if (thr_id < 0)
    {
        perror("thread create error : ");
        exit(0);
    }
    // 식별번호 p_thread 를 가지는 쓰레드를 detach 
    // 시켜준다. 
    pthread_detach(p_thread);
    pause();
    return 0;
}
			
위의 쏘쓰 코드에서 detach 시켰을때와 그렇지 않았을때의 메모리 상황을 비교해보기 바란다. detatach 를 했을경우 프로세스의 메모리 사용율과 detache 를 주석 처리했을경우의 메모리 사용율의 변화를 서로 비교해보면 되는데, detach 를 사용하지 않았을경우 t_function 이 종료가 되더라도 자원이 해제되지 않음을 볼수 있을것이다. 테스트는 간단한 스크립트를 이용하도록 한다.
[root@localhost test]# while [ 1 ]; do ps -aux | grep pthread | grep -v grep | grep -v vim; sleep 1; done
root      2668  0.0  0.1  1436  292 pts/8    S    18:37   0:00 ./pthread_detach
root      2668  0.0  0.1  1436  292 pts/8    S    18:37   0:00 ./pthread_detach
			
위의 ps 내용에서 5번째 필드의 변화를 확인하면 된다.


2.4. pthread_exit

void pthread_exit(void *retval);
			
pthread_exit 는 현재 실행중인 쓰레드를 종료시키고자 할때 사용한다. 만약pthread_cleanup_push 가 정의되어 있다면, pthread_exit 가 호출될경우 cleanup handler 가 호출된다. 보통 이 cleanup handler 은 메모리를 정리하는 등의 일을 하게 된다.

예제 : pthread_exit.c

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

// 쓰레드 함수
// 1초를 기다린후 아규먼트^2 을 리턴한다.
void *t_function(void *data)
{
    int num = *((int *)data);
    int i = 0;
    while(1)
    {
        if (i == 3)
            pthread_exit(0);
        printf("loop %d\n", i);
        i++;
        sleep(1);
    }
}

int main()
{
    pthread_t p_thread;
    int thr_id;
    int status;
    int a = 100;


    thr_id = pthread_create(&p_thread, NULL, t_function, (void *)&a);
    if (thr_id < 0)
    {
        perror("thread create error : ");
        exit(0);
    }
    pthread_join(p_thread, (void **)&status);
    return 0;
}
			


2.5. pthread_cleanup_push

void pthrad_cleanup_push(void (*routine) (void *), void *arg);
			
이것은 cleanup handlers 를 인스톨하기 위해서 사용된다. pthread_exit(3) 가 호출되어서 쓰레드가 종료될때 pthread_cleanup_push 에 의해서 인스톨된 함수가 호출된다. routine이 쓰레드가 종료될때 호출되는 함수이다. arg는 아규먼트이다.

cleanup handlers 는 주로 자원을 되돌려주거나, mutex 잠금등의 해제를 위한 용도로 사용된다. 만약 mutex 영역에서 pthread_exit 가 호출되어 버릴경우 다른쓰레드에서 영원히 block 될수 있기 때문이다. 또한 malloc 으로 할당받은 메모리, 열린 파일지정자를 닫기 위해서도 사용한다.

예제 : pthread_cleanup.c

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

// 쓰레드 함수
// 1초를 기다린후 아규먼트^2 을 리턴한다.
//

char *mydata;
void cleanup(void *);
void *t_function(void *data)
{
    int num = *((int *)data);
    int i = 0;
    int a = 1;
    // cleanup handler 로 cleanup 함수를 
    // 지정한다. 
    pthread_cleanup_push(cleanup, (void *)&a);
    mydata = (char *)malloc(1000);
    while(1)
    {
        if (i == 3)
        {
            // pthread_exit 가 호출되면서 
            // cleanup 을 호출하게 된다. 
            pthread_exit(0);
            return 1;
        }
        printf("loop %d\n", i);
        i++;
        sleep(1);
    }
    pthread_cleanup_pop(0);
}


int main()
{
    pthread_t p_thread;
    int thr_id;
    int status;
    int a = 100;


    thr_id = pthread_create(&p_thread, NULL, t_function, (void *)&a);
    if (thr_id < 0)
    {
        perror("thread create error : ");
        exit(0);
    }
    pthread_join(p_thread, (void **)&status);
    printf("Join finish\n");
}

// cleanup handler
void cleanup(void *myarg)
{
    printf("thread is clean up\n");
    printf("resource free\n");
    free(mydata);
}
			


2.6. pthread_cleanup_pop

pthread_cleanup_push 와 함께 사용되며, install 된 cleanup handler 을 제거하기 위해서 사용된다.

void pthread_cleanup_pop(int execute);
			
만약 execute 가 0 이라면, pthread_cleanup_push 에 의해 인스톨된 cleanup handler 를 (실행시키지 않고)삭제만 시킨다. 0 이 아닌 숫자라면 cleanup handler 을 실행시키고 삭제 된다. 사용예제는 2.5절을 참고하라.

그리고 pthread_cleanup_push 와 pthread_cleanup_pop 은 반드시 같은 함수내의 같은 레벨의 블럭에서 한쌍으로 사용해야 한다.


2.7. pthread_self

pthread_t pthread_self(void);
			
pthread_self를 호출하는 현재 쓰래드의 쓰레드식별자를 되돌려준다.

예제 : pthread_self.c

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

void *func(void *a)
{
    pthread_t id;
    id = pthread_self();
    printf("->%d\n", id);
}

int main(int argc, char **argv)
{
    pthread_t p_thread;
    pthread_create(&p_thread, NULL, func, (void *)NULL);
    printf("%d\n", p_thread);
    pthread_create(&p_thread, NULL, func, (void *)NULL);
    printf("%d\n", p_thread);

	return 1;
}
			


3. 쓰레드 동기화 함수

쓰레드 동기화와 관련된 함수들이다.


3.1. pthread_mutex_init

int pthread_mutex_init(pthread_mutex_t * mutex, 
           const pthread_mutex_attr *attr); 
			
mutex 는 여러개의 쓰레드가 공유하는 데이타를 보호하기 위해서 사용되는 도구로써, 보호하고자 하는 데이타를 다루는 코드영역을 단지 한번에 하나의 쓰레드만 실행가능 하도록 하는 방법으로 공유되는 데이타를 보호한다. 이러한 코드영역(하나의 쓰레드만 점유가능한)을 critical section 이라고 하며, mutex 관련 API 를 이용해서 관리할수 있다.

pthread_mutex_init 는 mutex 객체를 초기화 시키기 위해서 사용한다. 첫번째 인자로 주어지는 mutex 객체 mutex를 초기화시키며, 두번째 인자인 attr 를 이용해서 mutex 특성을 변경할수 있다. 기본 mutex 특성을 이용하기 원한다면 NULL 을 사용하면 된다.

mutex 특성(종류) 에는 "fast", "recurisev", "error checking" 의 종류가 있으며, 기본으로 "fast" 가 사용된다.

// 뮤텍스 객체 선언
pthread_mutex_t mutex_lock;
...
void *t_function()
{
    pthread_mutex_lock(&mutex_lock);
    // critical section
    pthread_mutex_unlock(&mutex_lock);
}
int main()
{
    pthread_t p_thread;
    int state;
    // 뮤텍스 객체 초기화, 기본 특성으로 초기화 했음
    pthread_mutex_init(&mutex_lock, NULL);
    pthread_create(&p_thread, NULL, t_function, (void *)&a);
    ...
    pthread_joinc(&p_thread, (void **)&status);
}
			


3.2. pthread_mutex_destory

int pthread_mutex_destory(pthread_mutex_t *mutex);
			
인자로 주어진 뮤텍스 객체 mutex 를 제거하기 위해서 사용된다. mutex  pthread_mutex_init()함수를 이용해서 생성된 뮤텍스 객체이다.

pthread_mutex_destory 를 이용해서 제대로 mutex 를 삭제하려면 이 mutex 는 반드시 unlock 상태이여야 한다.


3.3. pthread_mutex_lock

int pthread_mutex_lock(pthread_mutex_t *mutex);
			
pthread_mutex_lock 는 critcal section 에 들어가기 위해서 mutex lock 을 요청한다. 만약 이미 다른 쓰레드에서 mutex lock 를 얻어서 사용하고 있다면 다른 쓰레드에서 mutex lock(뮤텍스 잠금) 을 해제할때까지(사용할수 있을때까지) 블럭 된다.

만약 다른 어떤 쓰레드에서도 mutex lock 을 사용하고 있지 않다면, 즉시 mutex lock 을 얻을수 있게 되고 critcal section 에 진입하게 된다. critcal section 에서의 모든 작업을 마쳐서 사용하고 있는 mutex lock 이 더이상 필요 없다면 pthread_mutex_unlock 를 호출해서 mtuex lock 를 되돌려준다.


3.4. pthread_mutex_unlock

int pthread_mutex_unlock(pthread_mutex_t *mutex); 
			
critical section 에서의 모든 작업을 마치고 mutex lock 을 돌려주기 위해서 사용한다. pthread_mutex_unlock 를 이용해서 mutex lock 를 되돌려주면 다른 쓰레드에서 mutex lock 를 얻을수 있는 상태가 된다.


3.5. pthread_cond_init

int pthread_cond_init(pthread_cond_t *cond, 
                    const pthread_cond_attr *attr);
			
pthread_cond_init는 조견변수 (condition variable)cond를 초기화하기 위해서 사용한다. attr 를 이용해서 조건변수의 특성을 변경할수 있으며, NULL 을 줄경우 기본특성으로 초기화된다.

조건변수 cond는 상수 PTHREAD_COND_INITIALIZER 을 이용해서도 초기화 할수 있다. 즉 다음과 같은 2가지 초기화 방법이 존재한다.

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
or
pthread_cond_init(&cond, NULL);
			


3.6. pthread_cond_signal

int pthread_cond_signal(pthread_cond_t *cond);
			
조건변수 cond에 시그날을 보낸다. 시그날을 보낼경우 cond에서 기다리는(wait) 쓰레드가 있다면 쓰레드를 깨우게 된다(봉쇄가 풀림). 만약 조건변수 cond를 기다리는 쓰레드가 없다면, 아무런 일도 일어나지 않게되며, 여러개의 쓰레드가 기다리고 있다면 그중 하나의 쓰레드에게만 전달된다. 이때 어떤 쓰레드에게 신호가 전달될지는 알수 없다.


3.7. pthread_cond_boradcast

int pthread_cond_broadcast(pthread_cond_t *cond);
			
조건변수 cond에서 기다리는(wait) 모든 쓰레드에게 신호를 보내서, 깨운다는 점을 제외하고는pthread_cond_signal과 동일하게 작동한다.


3.8. pthread_cond_wait

int pthread_cond_wait(pthread_cond_t cond, pthread_mutex_t *mutex); 
			
조건변수 cond를 통해서 신호가 전달될때까지 블럭된다. 만약 신호가 전달되지 않는다면 영원히 블럭될수도 있다. pthread_cond_wait는 블럭되기 전에 mutex 잠금을 자동으로 되돌려준다.


3.9. pthread_cond_timewait

int pthread_cond_timedwait(pthread_cont_t *cond, pthread_mutex_t *mutex, 
                           const struct timespec *abstime);
			
조건변수 cond를 통해서 신호가 전달될때까지 블럭되며 자동으로 mutex을 돌려주는 점에서는pthread_cond_wait와 동일하다. 그러나 시간체크가 가능해서 abstime시간동안 신호가 도착하지 않는다면 error 를 발생하면서 리턴한다. 이때 리턴값은 ETIMEDOUT 이다. errno 가 세팅되는게 아닌, 리턴값으로 에러가 넘어오는것에 주의해야 한다.

또한 pthread_cond_timedwait함수는 다른 signal 에 의해서 interrupted 될수 있으며 이때 EINTR 을 리턴한다. 이 함수를 쓸때는 interrupted 상황에 대한 처리를 해주어야 한다.


3.10. pthread_cond_destroy

int pthread_cond_destroy(pthread_cond_t *cond);
			
pthread_cond_init를 통해서 생성한 조건변수cond에 대한 자원을 해제한다. destroy 함수를 호출하기 전에 어떤 쓰레드도 cond에서의 시그널을 기다리지 않는걸 확인해야 한다. 만약 cond 시그널을 기다리는 쓰레드가 존재한다면 이 함수는 실패하고 EBUSY 를 리턴한다.


3.11. 예제코드

이번장에서 설명한 쓰레드 동기화 관련 함수의 이해를 돕기 위해서 간단한 예제를 준비했다. 설명은 주석으로 대신한다.

예제 : pthrad_sync_api.c

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <iostream>

using namespace std;

void *ping(void *);
void *pong(void *);

pthread_mutex_t sync_mutex;
pthread_cond_t  sync_cond;

pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  gcond  = PTHREAD_COND_INITIALIZER;

int main()
{
    vector<void *(*)(void *)> thread_list;
    vector<pthread_t> tident(10); 
    int thresult;
    int status;
    int i;

    pthread_mutex_init(&sync_mutex, NULL);
    pthread_cond_init(&sync_cond, NULL);

    thread_list.push_back(pong);
    thread_list.push_back(ping);

    for(i = 0; i < thread_list.size(); i++ )
    {
        pthread_mutex_lock(&sync_mutex);
        if (pthread_create(&tident[i], NULL, thread_list[i], (void *)NULL) <0)
        {
            perror("error:");
            exit(0);
        }
        pthread_cond_wait(&sync_cond, &sync_mutex);
        pthread_mutex_unlock(&sync_mutex);
    }
    for (i = 0; i < tident.size(); i++)
    {
        pthread_join(tident[i], (void **)&status);
    }
}

void *ping(void *data)
{
    int i=0;
    pthread_mutex_lock(&sync_mutex);
    pthread_cond_signal(&sync_cond);
    pthread_mutex_unlock(&sync_mutex);
    while(1)
    {
        pthread_mutex_lock(&gmutex);
        printf("%d : ping\n", i);
        pthread_cond_signal(&gcond);
        pthread_cond_wait(&gcond, &gmutex);
        pthread_mutex_unlock(&gmutex);
        usleep(random()%100);
        i++;
    }
}

void *pong(void *data)
{
    int i = 0;
    pthread_mutex_lock(&sync_mutex);
    sleep(1);
    pthread_cond_signal(&sync_cond);
    pthread_mutex_unlock(&sync_mutex);
    while(1)
    {
        pthread_mutex_lock(&gmutex);
        pthread_cond_wait(&gcond, &gmutex);
        printf("%d : pong\n", i);
        pthread_cond_signal(&gcond);
        pthread_mutex_unlock(&gmutex);
        i++;
    }
}
			

위의 예제는 ping&pong 프로그램으로 ping 쓰레드와 pong 쓰레드가 각각 번갈아가면서 "ping", "pong" 을 날리는 프로그램이다. 2개의 영역에 걸쳐서 크리티컬섹션이 지정되어 있으며 각 크리티컬섹션안에는 쓰레드 동기화를 위해서 ptread_cond_signal 이 쓰여지고 있다.

위의 코드는 기본적으로 pong 쓰레드가 먼저 시그널을 대기하고 있다가 그 후 ping 쓰레드가 진입해서 "ping"을 날리고 시그널을 발생시키면 "pong" 메시지를 발생시키도록 되어 있다. 그렇다면 while 문에 있는 크리티컬 섹션에 반드시 pong 쓰레드가 먼저 진입할수 있도록 만들어줘야 할것이다. 그래서 위의 코드에서는 pong 쓰레드를 먼저 생성시켰다. 그러나 이것만으로는 충분하지 않다. 예를들어서 pong 쓰레드에서 크리티컬섹션에 들어가기 위해서 어떤 부가적인 작업이 있다고 했을때(메모리초기화, 기타 다른 함수 호출과 같은, 위에서는 sleep 으로 대신했다), 우리가 의도했던 바와는 다르게 ping 가 먼저 크리티컬섹션에 진입할수도 있다. 이럴경우 2개의 쓰레드는 교착상태에 빠지게 된다.

ping 쓰레드가 크리티컬섹션에 먼저 진입했을경우 ping 쓰레드는 "ping" 출력시키고 시그널을 발생시킬 것이고 pong 쓰레드가 "pong"를 출력시키고 시그널을 발생시킬때까지 시그널대기 하게 된다. ping 쓰레드가 시그널대기 하게 되면, 크리티컬섹션에 대한 뮤텍스 잠금이 해제됨으로 뒤늦게 크리티컬섹셔네 진입을 시도하던 pong 가 크리티컬섹션에 진입하고 ping 쓰레드에서부터 신호가 있는지 기다리게 될것이다. 그러나 ping 쓰레드는 이미 신호를 날려버렸음으로, pong 쓰레드는 결코 도착하지 않을 신호를 기다리며 영원히 시그널대기 하게 될것이다. 이런식으로 2개의 쓰레드는 교착상태에 빠져 버린다.

이 문제는 쓰레드간 동기화를 이용해서 해결할수 있으며, 위 코드에서는 mutex 잠금과, 조건변수를 이용해서 해결하고 있다. 물론 쓰레드간 동기화를 위해서 사용할수 있는 원시?적인 방법으로 sleep 나 usleep 같은 함수를 호출하는 방법도 있긴 하지만, ping 쓰레드에서 크리티컬 섹션에 진입하기전 1초 정도 sleep 을 주는 식으로 사용가능하지만 추천할만하진 않다. (간혹 간단하게 사용할수는 으며, 가장 확실한 방법을 제공해 주기도 한다)


4. Thread Attribute 함수

4.1. pthread_attr_init

int pthread_attr_init(pthread_attr_t *attr);
			
pthread_attr_init는 thread attribute 객체인 attr을 디폴트 값으로 초기화 시킨다.

성공할경우 0을 돌려주고 실패할경우 -1 을 되돌려준다.


4.2. pthread_attr_distroy

int pthread_attr_destroy(pthread_attr_t *attr);
			
pthread_attr_init에 의해 생성된 thread attribute 객체인 attr을 제거한다. 제거된 attr 을 다시 사용하기 위해서는 pthread_attr_init를 이용해서 다시 초기화 해주어야 한다.


4.3. pthread_attr_getscope

int pthread_attr_getscope(const pthread_attr_t *attr, 
             int *scope);
			
쓰레드가 어떤 영역(scope)에서 다루어지고 있는지를 얻어오기 위해서 사용된다. PTHREAD_SCOPE_SYSTEM과 PTHREAD_SCOPE_PROCESS 의 2가지 영역중에 선택할수 있다. SYSTEM 영역 쓰레드는 user 모드 쓰레드라고 불리우며, PROCESS 쓰레드는 커널모드 쓰레드라고 불리운다. 리눅스의 경우 유저모드 쓰레드인데, 즉 커널에서 쓰레드를 스케쥴링하는 방식이 아닌 쓰레드 라이브러리를 통해서 쓰레드를 스케쥴링 하는 방식을 사용한다.

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


int main()
{
    pthread_attr_t pattr;
    int scope;

    pthread_attr_init(&pattr);

    pthread_attr_getscope(&pattr, &scope);
    if (scope == PTHREAD_SCOPE_SYSTEM)
    {
        printf("user mode thread\n");
    }
    else if (scope ==  PTHREAD_SCOPE_PROCESS)
    {
        printf("Kernel mode thread\n");
    }

    return 1;
}
			
위 프로그램을 컴파일한후 Linux 에서 실행시키면 "user mode thread"를 출력하고 솔라리스 상에서 실행시키면 "kernel mode thread"를 출력한다.


4.4. pthread_attr_setscope

int pthread_attr_setscope(pthread_attr_t *attr, int scope);
			
쓰레드가 어떤 영역(scope)에서 작동하게 할것인지 결정하기 위해서 사용한다. 리눅스의 경우 Kernel mode 쓰레드를 지원하지 않음으로 오직 PTHREAD_SCOPE_SYSTEM 만을 선택할수 있다. 반면 솔라리스는 유저모드와 커널모드중 선택이 가능하다.

pthread_attr_setscope.c

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


int main()
{
    pthread_attr_t pattr;
    int scope;

    pthread_attr_init(&pattr);

    pthread_attr_setscope(&pattr, PTHREAD_SCOPE_PROCESS);
    pthread_attr_getscope(&pattr, &scope);
    if (scope == PTHREAD_SCOPE_SYSTEM)
    {
        printf("user mode thread\n");
    }
    else if (scope ==  PTHREAD_SCOPE_PROCESS)
    {
        printf("Kernel mode thread\n");
    }

    return 1;
}
			
위코드에서 쓰레드가 커널 모드에서 작동하도록 지정을 했다. 리눅스에서 실행시킬경우에는 비록 커널모드로 지정을 했다고 하더라도 유저모드 쓰레드로 작동하게 된다. 솔라리스의 경우에는 setscope 로 지정한대로 커널모드에서 작동하게 된다.


4.5. pthread_attr_getdetachstate

int pthread_attr_getdetachstate(pthread_attr_t *attr,
           int detachstate);
			
쓰레드가 join 가능한 상태(PTHREAD_CREATE_JOINABLE) 인지 detached 상태인지 (PTHREAD_CREATE_DETACHED) 인지를 알아낸다. 알아낸 값은 아규먼트 detachstate 에 저장된다.

기본은 PTHREAD_CREATE_JOINABLE 이며, pthread_detach를 이용해서 생성된 쓰레드를 detach 상태로 만들었을경우 또는 pthread_attr_setdetachstate함수를 이용해서 쓰레드를 detache 상태로 변경시켰을경우 PTHREAD_CREATE_DETACHED 상태가 된다.

예제 : pthread_attr_getdetachstate.c

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

pthread_attr_t attr;
void *test(void *a)
{
    int policy;
    printf("Thread Create\n");
    pthread_attr_getdetachstate(&attr, &policy);
    if (policy == PTHREAD_CREATE_JOINABLE)
    {
        printf ("Join able\n");
    }
    else if (policy == PTHREAD_CREATE_DETACHED)
    {
        printf ("Detache\n");
    }
}
int main()
{
    int status;
    pthread_t p_thread;
    pthread_attr_init(&attr);
    if (pthread_create(&p_thread, NULL, test, (void *)NULL) < 0)
    {
        exit(0);
    }

    pthread_join(p_thread, (void **)&status);
}
			
위의 프로그램을 실행시키면 분명 "Join able"를 출력할것이다.


4.6. pthread_attr_setdetachstate

int  pthread_attr_setdetachstate(pthread_attr_t *attr, 
             int detachstate);
			
쓰레드의 상태를 PTHREAD_CREATE_JOINABLE 혹은 PTHREAD_CREATE_DETACHED 상태로 변경시키기 위해서 사용된다. 아래와 같은 방법으로 사용하면 된다.
pthread_attr_t attr;
...
// JOINABLE 상태로 변경하고자 할때 
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
// DETACHED 상태로 변경하고자 할때
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
			


5. 쓰레드 시그널 관련

쓰레드간 프로세스와 쓰레드간 시그널 전달관련 API들이다. 자세한 내용은 쓰레드와 시그널을 참고하기 바란다.


5.1. pthread_sigmask

#include <pthread.h>
#include <signal.h>

int pthread_sigmask(int how, const sigset_t *newmask, sigset_t *oldmask);
			
쓰레드에서 시그널은 서로 공유된다. 그런이유로 만약 프로세스에 시그널이 전달되면 프로세스가 생성된 모든 쓰레드로 시그널이 전달된다. 그러나 특정 쓰레드만 시그널을 받도록 하고 싶을 때가 있을 것이다. 이경우 이 함수를 이용하면 된다.


5.2. pthread_kill

#include <pthread.h>
#include <signal.h>

int pthread_kill(pthread_t thread, int signo);
			
쓰레드 식별번호 thread signo 번호의 시그널을 전달한다.


5.3. sigwait

#include <pthread.h>
#include >signal.h>

int sigwait(const sigset_t *set, int *sig);
			
시그널 전달을 동기적으로 기다린다.


6. 쓰레드 취소

자세한 내용은 쓰레드 취소와 종료 pthread_cancel(3)을 참고하기 바란다. 여기에서는 인덱스만 제공한다.


6.1. pthread_cancel

#include <pthread.h>

int pthread_cancel(pthread_t thread);
			


6.2. pthread_setcancelstate

#include <pthread.h>

int pthread_setcancelstate(int state, int *oldstate);
			


6.3. pthread_setcancelstate

#include <pthread.h>

int pthread_setcancelstate(int state, int *oldstate);
			


6.4. pthread_setcanceltype

#include <pthread.h>

int pthread_setcanceltype(int type, int *oldtype);
			


6.5. pthread_testcancel

#include <pthread.h>

void pthread_testcancel(void);
			

참고 문서 #

  1. Pthread 뮤텍스와 조건변수
  2. pthread 1
  3. pthread 모델
  4. pthread 3
Posted by 두장
2008. 12. 23. 13:31

10진수

16진수

8진수

2진수

ASCII

10진수

16진수

8진수

2진수

ASCII

0

0×00

000

0000000

NULL

64

0×40

100

1000000

@

1

0×01

001

0000001

SOH

65

0×41

101

1000001

A

2

0×02

002

0000010

STX

66

0×42

102

1000010

B

3

0×03

003

0000011

ETX

67

0×43

103

1000011

C

4

0×04

004

0000100

EOT

68

0×44

104

1000100

D

5

0×05

005

0000101

ENQ

69

0×45

105

1000101

E

6

0×06

006

0000110

ACK

70

0×46

106

1000110

F

7

0×07

007

0000111

BEL

71

0×47

107

1000111

G

8

0×08

010

0001000

BS

72

0×48

110

1001000

H

9

0×09

011

0001001

HT

73

0×49

111

1001001

I

10

0×0A

012

0001010

LF

74

0×4A

112

1001010

J

11

0×0B

013

0001011

VT

75

0×4B

113

1001011

K

12

0×0C

014

0001100

FF

76

0×4C

114

1001100

L

13

0×0D

015

0001101

CR

77

0×4D

115

1001101

M

14

0×0E

016

0001110

SO

78

0×4E

116

1001110

N

15

0×0F

017

0001111

SI

79

0×4F

117

1001111

O

16

0×10

020

0010000

DLE

80

0×50

120

1010000

P

17

0×11

021

0010001

DC1

81

0×51

121

1010001

Q

18

0×12

022

0010010

SC2

82

0×52

122

1010010

R

19

0×13

023

0010011

SC3

83

0×53

123

1010011

S

20

0×14

024

0010100

SC4

84

0×54

124

1010100

T

21

0×15

025

0010101

NAK

85

0×55

125

1010101

U

22

0×16

026

0010110

SYN

86

0×56

126

1010110

V

23

0×17

027

0010111

ETB

87

0×57

127

1010111

W

24

0×18

030

0011000

CAN

88

0×58

130

1011000

X

25

0×19

031

0011001

EM

89

0×59

131

1011001

Y

26

0×1A

032

0011010

SUB

90

0×5A

132

1011010

Z

27

0×1B

033

0011011

ESC

91

0×5B

133

1011011

[

28

0×1C

034

0011100

FS

92

0×5C

134

1011100

\

29

0×1D

035

0011101

GS

93

0×5D

135

1011101

]

30

0×1E

036

0011110

RS

94

0×5E

136

1011110

^

31

0×1F

037

0011111

US

95

0×5F

137

1011111

_

32

0×20

040

0100000

SP

96

0×60

140

1100000

.

33

0×21

041

0100001

!

97

0×61

141

1100001

a

34

0×22

042

0100010

"

98

0×62

142

1100010

b

35

0×23

043

0100011

#

99

0×63

143

1100011

c

36

0×24

044

0100100

$

100

0×64

144

1100100

d

37

0×25

045

0100101

%

101

0×65

145

1100101

e

38

0×26

046

0100110

&

102

0×66

146

1100110

f

39

0×27

047

0100111

'

103

0×67

147

1100111

g

40

0×28

050

0101000

(

104

0×68

150

1101000

h

41

0×29

051

0101001

)

105

0×69

151

1101001

i

42

0×2A

052

0101010

*

106

0×6A

152

1101010

j

43

0×2B

053

0101011

+

107

0×6B

153

1101011

k

44

0×2C

054

0101100

'

108

0×6C

154

1101100

l

45

0×2D

055

0101101

-

109

0×6D

155

1101101

m

46

0×2E

056

0101110

.

110

0×6E

156

1101110

n

47

0×2F

057

0101111

/

111

0×6F

157

1101111

o

48

0×30

060

0110000

0

112

0×70

160

1110000

p

49

0×31

061

0110001

1

113

0×71

161

1110001

q

50

0×32

062

0110010

2

114

0×72

162

1110010

r

51

0×33

063

0110011

3

115

0×73

163

1110011

s

52

0×34

064

0110100

4

116

0×74

164

1110100

t

53

0×35

065

0110101

5

117

0×75

165

1110101

u

54

0×36

066

0110110

6

118

0×76

166

1110110

v

55

0×37

067

0110111

7

119

0×77

167

1110111

w

56

0×38

070

0111000

8

120

0×78

170

1111000

x

57

0×39

071

0111001

9

121

0×79

171

1111001

y

58

0×3A

072

0111010

:

122

0×7A

172

1111010

z

59

0×3B

073

0111011

;

123

0×7B

173

1111011

{

60

0×3C

074

0111100

< 

124

0×7C

174

1111100

|

61

0×3D

075

0111101

=

125

0×7D

175

1111101

}

62

0×3E

076

0111110

> 

126

0×7E

176

1111110

~

63

0×3F

077

0111111

?

127

0×7F

177

1111111

DEL

Posted by 두장

원문 http://blog.naver.com/ikaruce2/100018083359


stat and fstat는 한 프로세스가 기존 파일의 특성 값을 볼 수 있게 해 줍니다.

 

#include <sys/types.h>

#include <sys/stat.h>

 

int stat(cosnt char *pathname, struct stat *buf );

//파일에 연관된 정보를 얻음.

 

int fstat( int filedes, struct stat *buf );

// stat과 거의 유사하지만 파일이름 대신 파일 디스크립터를 사용

 

pathname : 파일 이름

buf : stat 구조를 가리키는 포인터

filedes : 파일 디스크립터

 

 

사용 예시

struct stat s;

int filedes, retval;

 

filedes = open("tmp/dina", O_RDWR );

 

retval = stat("/tmp/dina", &s );

 

retval = fstat(filedes, &s );

 

stat 스트럭처의 멤버는 다음과 같은 의미를 가집니다.

st_dev : 파일이 들어있는 논리적 장치

st_ino :파일의 inode 번호

st_mode : 파일 모드를 나타냄

st_nlink : 파일에 연관된 비(非) 심볼형 링크의 개수

st_uid, st_gid : 파일의 사용자 식별번호와 그룹 식별번호

st_rdev : 파일 엔트리가 장치를 기술

st_size : 파일의 현재 크기

st_atime : 파일이 마지막으로 읽혔던 시간

st_mtime : 파일이 마지막으로 변경된 시간

st_ctime : stat구조체의 멤버가 변경된 시간을 기록

st_blksize : 파일 시스템의 고유 I/O 블록 사이즈

st_blocks : 파일에 할당된 물리적 파일 시스템 블록의 수

Posted by 두장
2008. 12. 23. 13:24

원문 http://blog.naver.com/kimsk99/50003121515

STL을 공부하다보면 STL에서 검색이 필요한 자료구조는 모두 red-black tree로 되어 있다는 것을 알 수 있다.

그동안 대충 balanced tree의 일종이라는 것 정도만 알고 있었다.

최근에 red-black tree에 대해서 적어놓은 어떤 글을 보고 구체적으로 어떤 트리인지를 조사해 보았다.

 

이글은 아래의 싸이트에 나오는 설명을 참조해서 정리한 것이다.

 

  Sorting and Searching Algorithms

 

-----------------------------------------------------

 

Red-black tree(이하 RB트리)는 binary search tree를 확장시켜서 balance 기능을 추가 시킨 트리이다.

RB트리의 정의는 아래의 5가지이다.

 

속성 1. 모든 노드는 red나 black의 색깔을 갖는다.

속성 2. 모든 리프(leaf)는 센티넬 노드(sentinel node)로서 black이다.

속성 3. 만약 어떤 노드가 red라면 그 노드의 두 자식은 모두 black이다.

속성 4. 루트(root)에서 리프로의 경로를 생각할 때, 모든 경로에 대해서 black의 숫자는 같다. (이것을 black height라고 한다.)

속성 5. 루트는 항상 black이다.

 

RB트리는 위의 5가지 속성을 유지하도록 연산을 정의하는 것이다.

위의 5가지 속성에 의해서 RB트리는 leaf까지 최대 길이의 차이가 2배 범위 안에 놓이게 된다.

(이것은 B-B-B처럼 B로만 된 leaf까지의 경로가 최적의 경로이고, B-R-B-R-B처럼 RB가 섞인 경로가 최악이다.

이때 적의 경로와 최악의 경로의 차는 2배이다.)

5가지 속성을 유지하는데 드는 비용은 insert, delete에 평균적으로나 최악의 경우 log(n)이 들어간다.

 

RB 트리에서 리프를 센티넬 노드라고 표현하고 있다.

이것은 리프가 내부에 값을 갖는 노드가 아니라 일종의 더미 노드이기 때문이다.

보통 리프의 구현 즉 센티넬 노드의 구현은 공용 센티넬 노드를 만들고 그것을 참조하는 형태로 사용한다고 한다.

 

*. 노드 삽입

노드 삽입를 위해서 먼저 binary search tree에 대한 노드 삽입 알고리즘대로 노드를 일단 추가한다.

당연히 새롭게 삽입된 노드는 기존의 센티넬 노드의 자리에 추가되어야 한다.

즉 새 노드는 두개의 센티넬 노드를 자식 노드로 갖고 기존 센티넬 노드 자리에 들어가게 된다.

 

새롭게 삽입된 노드의 색깔은 무조건 R로 한다.

 

이렇게 된 트리는 현재  

 (1) 센티넬 노드 자리에 센티넬 노드를 자식으로 갖는 노드를 추가했기 때문에 속성 2, 5는 만족한다.

 (2) R로 했기 때문에 속성 1, 4를 만족한다.

 

위반할 수 있는 속성은 속성 3밖에 없다.

즉 다른 속성에 위반되지 않도록 하면서 속성 3을 만족하도록 트리를 변경해 주면된다.

 

속성 3을 위반하는 경우는 새로운 노드의 부모 노드가 R인 경우 밖에 없다.

이때에 대한 처리 방법은 아래의 2가지로 나뉜다.

 

A. 부모가 R, 삼촌도 R

부모가 R이라면 할아버지는 반드시 B여야 한다.

(기존 트리가 RB트리 속성을 모두 만족하기 때문에 연속된 R을 갖을 수는 없다.)

부모와 삼촌이 모두 R이라면 아래의 그림처럼 부모와 삼촌을 B로 바꾸고 할아버지를 R로 바꾼다.

이렇게 하면 모든 노드의 black height는 보전된다.

 

새롭게 생긴 R노드, 즉 할아버지 노드에 대해서는 지금 적용한 삽입후 처리 알고리즘을 재귀적으로 적용할 수 있다.

할아버지 노드는 R노드이고 B를 자식으로 갖기 때문에 새롭개 삽입된 노드의 특징을 모두 지니기 때문에 같은 알고리즘이 적용될 수 있다.

 

만약 할아버지 노드가 루트 노드라면 할아버지 노드를 다시 B로 칠하면 된다.

이 경우는 전체 트리의 black height가 1증가하게 된다.

 

 

 

 

B. 부모가 R, 삼촌은 B

부모가 R이고 삼촌이 B인경우는 아래의 그림과 같이 트리 전체를 한칸 밀어버리면 된다.

이렇게 밀어 버린다고 해서 binary search tree의 속성을 위배하지 않는다.

이 과정에서 모든 black height는 보전 된다.

 

 

위의 두가지 경우가 모든 경우의 수이다.

생각해보면 삼촌 노드가 존재하지 않는 경우가 있을 지도 모른다.

하지만 이런 경우는 트리의 정의에 의해서 없다.

모든 리프 노드를 일종의 더미 노드인 센티넬 노드로 만들어 버렸기 때문이다.

할아버지 노드에 값을 가지고 있는 아버지 노드가 존재하기 때문에 할아버지 노드는 센티넬 노드가 아니가.

그렇기 때문에 반드시 삼촌 노드가 존재해야 한다.

 

또 한가지 드는 의문점은 위의 그림에서 X가 A의 left가 아니라 right라면 어떻게 할 것이냐이다.

즉 위의 그림에서 c가 R인 경우로 위의 방법을 그대로 적용한다면 B, c가 R-R 이되어문제가 생긴다.

하지만 이런 때는 먼저 트리 이동을 수행해서 위의 그림처럼 X가 A의 left 상태로 만들 수 있다.

이것은 아래 그림의 두 트리는 모두 같은 의미의 binary search tree이기 때문에 가능하다.

(물론 위의 재배치가 가능한것도 이 특성을 이용한 것이다.)

 
 
*. 삭제
red-black tree의 노드 삭제 역시 기본은 binary search tree의 노드 삭제를 기본으로 한다.
Binary search tree의 삭제 원칙에 따라서 삭제될 노드는 항상 하나 이상의 센티넬 노드를 자식 노드로 갖는 노드가 된다.
이런 노드가 아닌 노드의 삭제시에는 연속자(succesor)를 대신 지우는 것으로 위의 조건을 만족하게 할 수 있다.
 
삭제 과정에서 지워진 노드가 R이라면 별도의 처리는 필요없이 모든 속성이 유지된다.
하지만 B라면 속성 4에 위배가 된다. (지워진 노드가 루트라면 예외이다.)
결국 속성 4를 다시 만족시켜 줘야 한다.
B가 지워졌기 때문에 black height가 1줄었다.
결국 다른 리프들에 대한 경로에 대한 black height가 1줄도록 변경해 줘야 한다.
이런 변경은 앞에서 본 삽입과정과 유사하게 처리될 수 있다.
Posted by 두장

 문자 평가 함수 

① 문자 평가 함수들은 한 개 문자의 종류를 검사 또는 평가하는 함수로서 1개의 문자가 숫자인지, 문자인지, 제어문자인지, 대문자, 소문자인지의 여부를 평가한다. 

② 문자평가함수는 is-로 시작되며, is-로 시작되는 함수는 헤더파일인"ctype.h"에 정의되어 있다. 

(1)isalnum( ) 함수 

① 영문자(A∼Z, a∼z) 또는 숫자(0∼9)인지를 평가하여 참이면 0이외의 값을 돌려주고, 거짓이면 0을 돌려준다.

② 형식 

#include <ctype.h>

int isalnum(c);

int c;


【 예제13-1 】하나의 문자를 읽어 들여 영문자 또는 숫자이면 alphanumeric를 출력하는 프로그램(ch13-1.c)

▶ 프로그램

#include <stdio.h>

#include <ctype.h>

main()

{

int c;

clrscr(); 

printf("Input data : ");

c = getchar(); 

if(isalnum(c) != 0)

printf("\n %c = alphanumeric\n", c);

else

printf("\n %c = Not alphanumeric\n", c);

getch();

}

▶ 실행결과

Input data : a

a = alphanumeric

Input data : #

a = Not alphanumeric



(2)isalpha( ) 함수 

① 영문자(A∼Z, a∼z)인가를 평가하여 참이면 0이외의 값을 돌려주고, 거짓이면 0을 돌려준다.

② 형식

#include <ctype.h>

int isalpha(c);

int c;


【 예제13-2 】하나의 문자를 읽어 들여 영문자이면 alphabetic을 출력하는 프로그램(ch13-2.c)

▶ 프로그램

#include <stdio.h>

#include <ctype.h>

main()

{

int c;

clrscr(); 

printf("Input data : ");

c = getchar(); 

if(isalpha(c) != 0)

printf("\n %c = alphabetic\n", c);

else

printf("\n %c = Not alphabetic\n", c); 

getch();

}

▶ 실행결과

Input data : a

a = alphabetic

Input data : 3

a = Not alphabetic



(3) iscntrl( ) 함수 

① 제어문자인가를 평가하여 제어문자로 참이면 0이외의 값을 돌려주고, 거짓이면 0을

돌려준다.

② 형식

#include <ctype.h>

int iscntrl(c);

int c;


【 예제13-3 】하나의 문자를 읽어 들여 제어문자이면 Input data is control character를 출력하는 프로그램(ch13-3.c)

▶ 프로그램

#include <stdio.h>

#include <ctype.h>

main()

{

int c;

clrscr(); 

printf("Input data :");

c = getchar(); 

if(iscntrl(c) != 0)

printf("input data is conrol character\n");

else

printf("input data is not control character\n"); 

getch();

}

▶ 실행결과

Input data : a

Input data is not control character

Input data : ^A

Input data is control character



(4) isdigit( ) 함수 

① 0∼9인가를 평가하여 참이면 0이외의 값을 돌려주고, 거짓이면 0을 돌려준다.

② 형식

#include <ctype.h>

int isdigit(c);

int c;


【 예제13-4 】하나의 문자를 읽어들여 숫자이면 digit을 출력하는 프로그램(ch13-4.c)

▶ 프로그램

#include <stdio.h>

#include <ctype.h>

main()

{

int c;

clrscr();

printf("Input data :"); 

if(isdigit(c) != 0)

printf("\n %d = digit\n", c);

else

printf("\n %d = Not digit\n", c); 

getch();

}

▶ 실행결과

Input data : a

a = Not digit 

Input data : 9

a = digit



(5) isupper( ) 함수 

① 영문 대문자(A∼Z)인가를 평가하여 참이면 0이외의 값을 돌려주고, 거짓이면 0을 돌려준다.

② 형식

#include <ctype.h>

int isupper(c);

int c;

【 예제13-5 】하나의 문자를 읽어 들여 영문 대문자이면 upper alphabetic을 평가하는 프로그램(ch13-5.c)

▶ 프로그램

#include <stdio.h>

#include <ctype.h>

main()

{

int c;

clrscr();

printf("Input data :"); 

c = getchar(); 

if(isupper(c) != 0)

printf("\n %c = upper alphabetic\n", c);

else

printf("\n %c = Not upper alphanumeric\n", c); 

getch();

}

▶ 실행결과

Input data : a

a = Not upper alphabetic

Input data : A

a = upper alphabetic



(6) islower( ) 함수 

① 영문 소문자(a∼z)인가를 평가하여 참이면 0이외의 값을 돌려주고, 거짓이면 0을

돌려준다.

② 형식

#include <ctype.h>

int islower(c);

int c;

【 예제13-6 】다음과 같이 선언한 문자 중에서 소문자들을 출력하는 프로그램(ch13-6.c)

char a[9] = {'K', 'b', 's', 'M', 'b', 'c', 'S', 'b', 's'};

▶ 프로그램

#include <stdio.h>

#include <ctype.h>

main()

{

char c[9] = {'K', 'b', 's', 'M', 'b', 'c', 'S', 'b', 's'};

int i;

clrscr();

for(i=0; i<9; i++)

if(islower(c[i]) != 0)

printf("%c", c[i]);

getch();

}

▶ 실행결과

bsbcbs



(7) isspace( ) 함수 

① 공백인가를 평가하여 참이면 0이외의 값을 돌려주고, 거짓이면 0을 돌려준다.

② 형식

#include <ctype.h>

int isspace(c);

int c;


【 예제13-7 】공백이 입력되기 전까지 문자를 출력하는 프로그램(ch13-7.c)

▶ 프로그램

#include <stdio.h>

#include <ctype.h>

main()

{

char c;

clrscr();

printf("Input string : ");

c = getchar();

do

{

putchar(c);

c = getchar();

}

while(isspace(c) == 0);

getch();

}

▶ 실행결과

Input string : seoul korea

seoul




13-2. 문자 변환 함수 

① 소문자를 대문자로, 대문자를 소문자로 변환하거나 또는 ASCII 코드가 아닌 값을 ASCII 코드 값으로 바꾸어 준다.

② 문자변환함수는 헤더파일인 "ctype.h"에 정의되어 있다. 

(1) toascii( ) 함수 

① ASCII 코드로 변환시켜 주는 함수

② 형식

#include <ctype.h>

int toascii(c);

int c;


【 예제13-8 】지정 문자를 ASCII 코드로 변환시켜 주는 프로그램(ch13-8.c)

▶ 프로그램

#include <stdio.h>

#include <ctype.h>

main()

{

int c;

clrscr(); 

printf("Input character :");

c = getchar();

printf("ASCII CODE = %x\n", toascii(c));

printf("DECIMAL NUMBER = %d\n", c);

getch();

}

▶ 실행결과

Input character : A

ASCII CODE = 41

DECIMAL NUMBER = 65

(2) tolower( ) 함수 

① c가 대문자이면 소문자로 변환시켜 준다.

② 형식 

#include <ctype.h>

int tolower(c);

int c;



【 예제13-9 】지정문자가 대문자이면 소문자로 변환시켜 주는 프로그램(ch13-9.c)

▶ 프로그램

#include <stdio.h>

#include <ctype.h>

main()

{

int c;

clrscr(); 

printf("Input sring :"); 

while((c = getchar()) != EOF)

putchar(tolower(c)); 

getch();

}

▶ 실행결과

input string : MBC

mbc


(3) toupper( ) 함수 

① c가 소문자이면 대문자로 변환시켜 준다.

② 형식 

#include <ctype.h>

int toupper(c);

int c;



【 예제13-10 】지정문자가 소문자이면 대문자로 변환시켜 주는 프로그램(ch13-10.c)

▶ 프로그램

#include <stdio.h>

#include <ctype.h>

main()

{

int c;

clrscr(); 

printf("Input sring :"); 

while((c = getchar()) != EOF)

putchar(toupper(c)); 

getch();

}

▶ 실행결과

input string : mbc

MBC


13-3. 문자열의 조작함수 

문자열 조작 함수는 헤더파일인 "string.h"에 정의되어 있다. 

(1) strcpy( ) 함수 및 strncpy( ) 함수 

① 문자열을 복사하는 함수

② 형식

#include <string.h>

char *strcpy(cd, str);

char *strncpy(cd, str, len);

char *cd; ☞ 문자열이 복사되는 배열

char *str; ☞ 복사하는 문자열

int len; ☞ 복사하는 문자열의 길이

③ strcpy( )함수

"cd"가 가리키는 버퍼에 "str"이 가리키는 문자열을 복사하고, 함수값으로 "cd"의 값을 반환한다.

④ strncpy( ) 함수

앞에서부터 최대 len 문자까지를 복사한다. 이때 "cd"는 "복사 문자열의 길이 + 1"크기 이상을 확보해 두어야 한다. 

【 예제13-11 】문자열 str을 배열 cd에 copy하는 프로그램(ch13-11.c)

▶ 프로그램

#include <stdio.h>

#include <string.h>

main()

{

char cd[20] = " ";

char *str="Temple of the king";

clrscr();

printf("Before cd[20] data = %s\n", cd);

strcpy(cd, str);

printf("After cd[20] data = %s\n", cd);

getch();

}

▶ 실행결과

Before cd[20] data =

After cd[20] data = Temple of the king

【 예제13-12 】문자열 str에서 6개의 문자를 cd에 복사하는 프로그램(ch13-12.c)

▶ 프로그램

#include <stdio.h>

#include <string.h>

main()

{

char *cd;

char *str="Temple of the king";

int len = 6;

clrscr(); 

printf("Original *str data = %s\n", str);

printf("Before cd[20] data = %s\n", cd); 

strncpy(cd, str, len);

printf("After cd[20] data = %s\n", cd); 

getch();

}

▶ 실행결과

Original *str data = Temple of the king

Before cd[20] data =

After cd[20] data = Temple



(2) strcat( ) 및 strncat( ) 함수 

① 문자열을 연결시키는 함수

② 형식

#include <string.h>

char *strcat(buf, str);

char *strncat(buf, str, len);

char *cd;

char *str;

char *buf;

int len;

③ strcat( ) 함수

"buf"가 가리키는 버퍼에 들어있는 문자열의 뒷부분에 "str"이 가리키는 문자열을 연결시킨다.

④ strncat( ) 함수

"str"이 가리키는 문자열의 최대 "len" 문자까지만 "buf"가 가리키는 버퍼의 문자열에 연결시켜 준다.

【 예제13-13 】문자열 buf와 문자열 str을 연결하여 출력하는 프로그램이다.(ch13-13.c)

▶ 프로그램

#include <stdio.h>

#include <string.h>

main()

{

char *buf = "Temple";

char *str=" of the king";

clrscr(); 

printf("Original *buf data = %s\n", buf);

printf("Original *str data = %s\n", str); 

strcat(buf, str);

printf("After *buf data = %s\n", buf);

getch();

}

▶ 실행결과

Original *buf data = Temple

Original *str data = of the king

After *buf data = Temple of the king


【 예제13-14 】문자열 buf와 문자열 str의 11개의 문자를 연결하는 프로그램(ch13-14.c)

▶ 프로그램

#include <stdio.h>

#include <string.h>

main()

{

char *buf = "Temple";

char *str=" of the king";

char *p;

int len = 11;

clrscr(); 

printf("Original *buf data = %s\n", buf);

printf("Original *str data = %s\n", str); 

p = strncat(buf, str, len); 

printf("After *p data = %s\n", p);

getch();

}

▶ 실행결과

Original *buf data = Temple

Original *str data = of the king

After *p data = Temple of the kin

(3) strchr( ) 함수 및 strrchr( ) 함수 

① 지정된 문자의 최초 위치 탐색과 최후 위치 탐색을 위한 함수

② 형식

#include <string.h>

char *strchr(string, c);

char *strrchr(string, c);

char *string; ☞ 탐색되는 문자열

char *c; ☞ 찾고 싶은 문자열

③ strchr( ) 함수

"string"내에서 찾고 싶은 문자 c의 내용이 발견되면 발견된 최초의 위치를 반환하고 발견되지 않으면 null 값을 반환한다. 즉, 최초의 문자가 발견된 위치를 반환시켜 준다.

④ strrchr( ) 함수

"string"내에서 찾고 싶은 문자 c의 내용이 발견되면 발견된 최후 위치를 반환하고 발견되지 않으면 null값을 반환한다. 즉, 최후의 문자가 발견된 위치를 반환시켜 준다. 

【 예제13-15 】문자열 string에서 e가 발견된 최초의 주소를 구하는 프로그램(ch13-15.c)

▶ 프로그램

#include <stdio.h>

#include <string.h>

main()

{

char *string="Temple of the king";

char *c;

clrscr(); 

printf("*string data = %s\n", string);

printf("*string, address = %x\n", string); 

c = strchr(string, 'e');

printf("'e', address = %x\n", c);

printf("c = %s\n", c); 

getch();

}

▶ 실행결과

*string data = Temple of the king

*string, address = 194

'e', address = 195

【 예제13-16 】문자열 string에서 e가 기억된 마지막 주소를 구하는 프로그램(ch13-16.c)

▶ 프로그램

#include <stdio.h>

#include <string.h>

main()

{

char *string="Temple of the king";

char *c;

clrscr();

printf("*string data = %s\n", string);

printf("*string, address = %x\n", string); 

c = strrchr(string, 'e');

printf("last address,'e' = %x\n", c);

getch();

}

▶ 실행결과

*string data = Temple of the king

*string, address = 194

last address,'e' = 1a0



(4) strcmp( ) 함수 및 strncmp( ) 함수 

① 문자열을 비교하는 함수로 앞에서 한 개의 문자씩 사전식 순서로 비교하여 그 결과를

반환한다.

② 형식

#include <string.h>

int strcmp(string1, string2);

int strncmp(string1, string2, size);

char *string1, *string2; ☞ 비교할 문자열

unsigned size; ☞ 비교할 길이

③ 2개의 문자열을 비교한 후 다음과 같이 결과를 반환한다.

  • string1 > string2 이면 양수값을 반환한다.
  • string1 = string2 이면 0을 반환한다.
  • string1 < string2 이면 음수값을 반환한다.

④ strcmp(p, q) 함수

문자열 p와 q를 비교하여 p의 문자열이 알파벳순으로 q보다 앞이면 음수값, 뒤이면 양수값을 반환한다.

【 예제13-17 】문자열 string1과 문자열 string2를 비교하는 프로그램(ch13-17.c)

▶ 프로그램

#include <stdio.h>

#include <string.h>

main()

{

char *string1="America";

char *string2="Korea";

int i;

clrscr(); 

printf("*string1 data = %s\n", string1);

printf("*string2 data = %s\n", string2); 

i = strcmp(string1, string2);

printf("value i = %d\n", i); 

if(i != 0)

printf("string1 not equal to string2\n");

else

printf("string1 equal to string2\n"); 

getch();

}

▶ 실행결과

*string1 data = America

*string2 data = Korea

value i = -10

string1 not equal to string2


【 예제13-18 】문자열 string1과 문자열 string2를 비교하는 프로그램(ch13-18.c)

▶ 프로그램

#include <stdio.h>

#include <string.h>

main()

{

char *string1="Banana";

char *string2="Bingo";

int i;

unsigned len = 5;

clrscr(); 

printf("*string1 data = %s\n", string1);

printf("*string2 data = %s\n", string2); 

i = strncmp(string1, string2, len);

printf("value i = %d\n", i); 

if(i != 0)

printf("string1 not equal to string2\n");

else

printf("string1 equal to string2\n"); 

getch();

}

▶ 실행결과

*string1 data = Banana

*string2 data = Bingo

value i = -8

string1 not equal to string2

(5) strset( )함수 및 strnset( ) 함수 

① 문자열을 변경시키는 함수

② 형식

#include <string.h>

char *strset(string, c);

char *strnset(string, c, len);

char *string;

int c;

int len;

③ strset( ) 함수

문자열 string이 가리키는 곳의 모든 문자를 c와 같은 문자로 전부 변경시킨다.

④ strnset( ) 함수

문자열 string이 가리키는 곳의 첫 번째 문자부터 n개의 문자만 c와 같은 문자로 전부 변경시킨다. 

【 예제13-19 】포인터 string이 가리키는 "SONATA" 문자열을 "KKKKKK"로 바꾸는 프로그램(ch13-19.c)

▶ 프로그램

#include <stdio.h>

#include <string.h>

main()

{

char *string="SONATA";

clrscr(); 

printf("*string data = %s\n", string); 

strset(string, 'K');

printf("after string data = %s\n", string); 

getch();

}

▶ 실행결과

*string data = SONATA

after string data = KKKKKK

【 예제13-20 】포인터 string이 가리키는 "SONATA" 문자열을 "SONKKK"로 바꾸는 프로그램(ch13-20.c)

▶ 프로그램

#include <stdio.h>

#include <string.h>

main()

{

char *string="SONATA";

clrscr(); 

printf("*string data = %s\n", string); 

strset(string+3, 'K');

printf("after string data = %s\n", string); 

getch();

}

▶ 실행결과

*string data = SONATA

after string data = SONKKK


【 예제13-21 】포인터 string이 가리키는 "SONATA" 문자열을 "KKKATA"로 바꾸는 프로그램(ch13-21.c)

▶ 프로그램

#include <stdio.h>

#include <string.h>

main()

{

char *string="SONATA";

int len = 3;

clrscr(); 

printf("*string data = %s\n", string); 

strnset(string, 'K', len);

printf("after string data = %s\n", string); 

getch();

}

▶ 실행결과

*string data = SONATA

after string data = KKKATA

(6)strlen( ) 함수 

① 문자열의 길이를 알아내는 함수

② "\0"을 포함하지 않은 문자열 길이를 함수값으로 반환시켜 준다.

③ 형식

#include <string.h>

unsigned strlen(string);

char *string;


【 예제13-22 】문자열 string의 문자열 길이를 구하는 프로그램(ch13-22.c)

▶ 프로그램

#include <stdio.h>

#include <string.h>

main()

{

char *string="SONATA";

int len ;

clrscr(); 

printf("*string data = %s\n", string); 

len = strlen(string);

printf("string length = %d\n", len); 

getch();

}

▶ 실행결과

*string data = SONATA

string length = 6


(7) atoi( ) 함수 

① 문자열을 정수로 변환시키는 함수

② 형식

#include <stdlib.h>

int atoi(string);

char *string;


【 예제13-23 】문자열 string1과 문자열 string2를 정수로 변환하여 덧셈하는 프로그램(ch13-23.c)

▶ 프로그램

#include <stdio.h>

#include <stdlib.h>

main()

{

char *string1="1234";

char *string2="4321";

int i;

clrscr(); 

printf("*string1 data = %s\n", string1);

printf("*string2 data = %s\n", string2); 

i = atoi(string1) + atoi(string2);

printf("after integer data = %d\n", i); 

getch();

}

▶ 실행결과

*string1 data = 1234

*string2 data = 4321

after integer data = 5555

(8) strlwr( ) 함수, strupr( ) 함수, strrev( ) 함수 

① strlwr( ) 함수 : 소문자화 하는 함수 

【 예제13-24 】"KBS"를 "kbs"로 변환하는 프로그램(ch13-24.c)

▶ 프로그램

#include <stdio.h>

#include <stdlib.h> 

main()

{

char *string1="KBS";

char *string2;

clrscr(); 

printf("*string1 data = %s\n", string1);

string2 = strlwr(string1);

printf("*string2 data = %s\n", string2);

getch();

}

▶ 실행결과

*string1 data = KBS

*string2 data = kbs


② strupr( ) 함수 : 대문자화 하는 함수 

【 예제13-25 】"kbs"를 "KBS"로 변환하는 프로그램(ch13-25.c)

▶ 프로그램

#include <stdio.h>

#include <stdlib.h>

main()

{

char *string1="kbs";

char *string2;

clrscr(); 

printf("*string1 data = %s\n", string1);

string2 = strupr(string1);

printf("*string2 data = %s\n", string2);

getch();

}

▶ 실행결과

*string1 data = kbs

*string2 data = KBS

③ strrev( ) 함수 : 문자 배열을 반대로 하는 함수 

【 예제13-26 】"Computer"를 "retupmoC"로 변환하는 프로그램(ch13-26.c)

▶ 프로그램

#include <stdio.h>

#include <stdlib.h>

main()

{

char *string1="Computer";

char *string2;

clrscr(); 

printf("*string1 data = %s\n", string1); 

string2 = strrev(string1);

printf("*string2 data = %s\n", string2); 

getch();

}

▶ 실행결과

*string1 data = Computer

*string2 data = retupmoC


 

13-4. 문자열 변환 함수 

출력하고자 하는 문자열을 주어진 형식에 따라 편집하여 주는 함수 

(1) sprintf( ) 함수 

① 문자열을 지정된 서식에 따라 출력을 변환하는 함수

② 형식

#include <stdlib.h>

int sprintf(buffer, format, 출력자료1, 출력자료2, …);

char *buffer; ☞ 출력할 문자열

char *format; ☞ 서식 제어 문자열

③ 출력 자료들을 "format"에 표시된 변환 규칙에 따라 buffer에 넣는다. 편집이 끝난 후에는 '\0'을 제일 끝에 추가시킨다. 

【 예제13-27 】다음 프로그램을 실행하라.(ch13-27.c)

char buf1[10], buf2[14], *p = "KOREA";

① sprintf(buf1, "seoul%d%c", 123, '!');

buf1의 내용:

s

e

o

u

l

1

2

3

!

\0

② sprinf(buf2, "%-5s = %5d", p, 123);

buf2의 내용:

K

O

R

E

A


=




1

2

3

\0

▶ 프로그램

#include <stdio.h>

#include <stdlib.h>

main()

{

char buf1[10], buf2[14];

char *p="KOREA";

clrscr();

sprintf(buf1, "seoul%d%c", 123, '!');

puts(buf1);

sprintf(buf2, "%5s = %5d",p, 123);

puts(buf2);

getch();

}

▶ 실행결과

seoul123!

KOREA = 123

(2) sscanf( ) 함수 

① 표준 입력에서 서식 지정된 값을 읽어 들인다.

② 형식

#include <stdlib.h>

int sscanf(buffer, format, 입력자료1, 입력자료2, …);

char *buffer; ☞ 입력할 문자열

char *format; ☞ 서식 제어 문자열

③ "buffer" 내의 자료를 "format"에 표시된 변환 규칙에 따라 입력자료1, 입력자료2, … 등에 입력시켜 준다. 이 함수의 반환 값은 입력이 성공되면 입력 변수의 개수가 되며, 실패시에는 -1이 된다. 

【 예제13-28 】다음 프로그램을 실행하여라.(ch13-28)

char *buf = "KOREA 12345 3.14", name[10];

float f;

int i, j;

sscanf(buf, "%s%3d%d%f", name, &i, &j, &f);

▷ buf의 내용 :

K

O

R

E

A



1

2

3

4

5


3

.

1

4

\0

▷ 문자 변수 name의 내용 :

K

O

R

E

A

\0

▷ 정수형 변수 i, j와 실수형 변수 f의 내용 :

i의 내용 :

1

2

3

 

j의 내용 :

4

5

f의 내용 :

3

.

1

4

▶ 프로그램

#include <stdio.h>

#include <stdlib.h>

main()

{

char *buf = "KOREA 12345 3.14", name[10];

float f;

int i, j;

clrscr();

sscanf(buf, "%s%3d%d%f", name, &i, &j, &f);

printf("name = %s\ni = %d\nj = %d\nf = %.2f\n", name, i, j, f);

getch();

}

▶ 실행결과

name = KOREA

i = 123

j = 45

f = 3.14

 

출처:http://www.daejin.or.kr/home/hslee2/TC/chap13/chap13-1.htm

Posted by 두장
원문 http://blog.naver.com/pjfile/40012816089

22 프로세스의 시동과 종료

    22.1 프로그램 인수들
    22.2 환경 변수들
    22.3 프로그램 종료

 

∵ getopt() 함수란?  유닉스에서는 프로그램 실행시 옵션으로 '-a','-b'등등을 줄 수 있는 것이

                             많은데 이 처리를 쉽게하기 위해 만들어진 함수가 입니다.
 

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

 

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

 

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

--------------------------------------------------------------------------------

 

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

Posted by 두장
2008. 12. 22. 11:35

SNMP

윤 상배

dreamyun@yahoo.co.kr

교정 과정
교정 0.8 2003년 4월 20일 21시
최초 문서작성


1절. 소개

개인적으로 최근들어 SNMP에 관심을 가지게 되었다. (실은 상당히 오래되었지만) 그래서 앞으로 몇부? 에 걸쳐서 SNMP관련 강좌를 개설하고자 한다. 강좌는 SNMP개요및 설치운용에서 부터 시작해서 프로그래밍을 통해서 SNMP응용 애플리케이션을 제작하고, 확장 MIB(뒤에 설명한다)를 작성하는 것 까지를 다룰것이다.

이번글은 그중 첫번째 글로 SNMP개요와 설치및 운용에 대한 글이다. 설치및 운용은 실제 어떻게 작동되는지 눈으로 확인하는 차원의 수준에서 이루어질 것이며, 설치되는 snmp애플리케이션의 상세설치와 높은 수준에서의 운용에 대해서는 언급하지 않을것이다. 이러한 것들은 (필요할경우)해당 snmp애플리케이션의 메뉴얼을 참고해서 개인적으로 학습해야만 할것이다.

여기에서 얻은 지식은 나중에 SNMP애플리케이션을 제작하는 밑거름이 될것이다.


2절. SNMP개요

2.1절. SNMP란 무엇인가

SNMP는 Simple Network Management Protocol의 약자이다. 해석을 해보자면 간단한 네트워크관리를 위한 규약 인데, 말그대로 SNMP는 네트워크관리를 위한 용도로 사용되는 프로토콜이다. 가장 앞에 Simple라는 단어가 붙어있는데, 진짜로 간단한 프로토콜인지 아닌지는 사람에 따라 약간씩 차이가 있을수 있다. 필자가 보기엔 그리 복잡한 프로토콜은 아닌것 같은데, 어떤 사람들은 매우 복잡한 프로토콜 이라고 말하는 사람들도 있다.

그럼 먼저 SNMP가 나타난 배경에 대해서 알아보도록 하겠다. SNMP가 쓰이기 전에 일반적으로 사용되는 네트워크 관리는 ICMP에 의존했었다. ICMP는 Network계층의 프로토콜로써, 운영체제에 관계없이 사용할수 있는 간단한 프로토콜이였다. 이 프로토콜을 이용해서 우리는 네트워크로 연결된 각각의 호스트가 작동하고 있는지, 작동한다면 어느정도의 응답시간을 가지고 작동하는지 등의 간단한 정보를 얻을수 있었으며, 초기에는 이정도로도 필요한 네트워크 관리가 가능했었다. ICMP를 이용한 가장 유용한 도구는 아마도 ping 프로그램일 것이다.

그러나 인터넷의 사용이 보편화되고 네트워크에 연결된 호스트의 수가 증가하자 거기에 따라서 네트워크 구성역시 복잡해지고, ICMP만을 가지고는 이러한 네트워크의 관리를 효율적으로 할수 없게 되었다.

그래서 몇가지 프로토콜에 대한 연구가 진행되었고, SGMP, HIMS, CMIP/CMIS등이 제안되게 되었다. 이중에서 SGMP를 발전시킨 SNMP가 사실상 네트워크 관리를 위한 표준적인 프로토콜로 자리잡게 되었다. 다른 프로토콜들이 사용되지 않은데에는 몇가지 이유가 있었다. CMIP/CMIS는 너무 방대하고 너무 복잡했으며, HEMS의 경우에는 실제 적용사례가 적었기 때문이다.

어쨋든 SNMP는 거의 대부분의 운영체제에서 사용되어 지고 있다. 여러분이 사용하는 Linux, 그밖의 대부분의 유닉스와, 윈도우계열 운영체제는 기본적으로 SNMP프로토콜을 사용하는 도구들을 제공하고 있다. 그외에도 router등 TCP/IP를 네트워크 프로토콜로 사용되는 운영체제들 역시 SNMP는 필수적으로 제공하고 있다.


2.2절. SNMP로 할수 있는 것들

SNMP를 이용해서 할수 있는 것들은 다음과 같다.

네트워크 구성관리

네트워크상의 호스트들이 어떤 구조를 이루고 있는지 지도를 그리는게 가능하다.

성능관리

각 네트워크 세그먼트간 네트워크 사용량, 에러량, 처리속도, 응답시간 등 성능 분석에 필요한 통계정보를 얻어낼수 있다.

장비관리

SNMP의 주목적이 네트워크관리관리 이기는 하지만 SNMP특유의 유연한 확장성을 이용하여서 시스템정보(CPU, MEMORY, DISK 사용량)의 정보를 얻어올 수 있도록 많은 부분이 확장되었다. 이 정보는 네트워크문제를 해결하는데 큰도움을 준다. 예를들어 특정 세그먼트의 네트워크 사용량이 갑자기 급증했는데, 특정 호스트의 CPU사용율까지 갑자기 증가했다면, 우리는 해당 호스트에서 문제가 발생했을것이란걸 유추해낼수 있을것이다.

보안관리

정보의 제어 및 보호 기능, 최근버젼인 SNMP3는 특히 정보보호를 위한 기능이 향상되었다.


2.3절. SNMP를 통한 망의 구성

SMTP는 인터넷상에서 메시지를 교환하기 위한 프로토콜로 사용되며, 주로 전자메일 교환을 위해서 사용되는 프로토콜이다. 그러나 SMTP는 어디까지나 프로토콜일 뿐이며, 실제 메시지를 인터넷상에서 주고 받기 위해서는 SMTP프로토콜을 사용하는 SMTP서버(Sendmail같은)와 SMTP클라이언트(mutt, pine같은)가 준비되어 있어야만 한다.

SNMP역시 그자체로는 프로토콜일 뿐이며 SNMP프로토콜을 활용해서 실제 네트워크 관리 정보를 얻어오기 위해서는 응용 애플리케이션이 준비되어있어야만 한다. 보통의 네트워크프로토콜을 사용하는 애플리케이션이 서버/클라이언트 모델로 구성되듯이 SNMP역시 서버와 클라이언트로 구성된다.

그림 1. SNMP망 관리 시스템

일반적으로 SNMP망 에서는 서버/클라이언트라고 부르지 않고 snmp manager/snmp agent라고 부른다. snmp agent는 관리대상이 되는 시스템에 설치되어서 필요한 정보(네트워크 혹은 시스템)를 수집하기 위한 snmp 모듈(혹은 애플리케이션) 이며, snmp manager은 snmp agent가 설치된 시스템에 필요한 정보를 요청하는 snmp 모듈이다. snmp agent는 서버, snmp manager은 클라이언트로 생각하면 이해하기가 좀더 수월할 것이다(그러나 반드시 agent가 서버, manager이 클라이언트가 되는건 아니다. 그냥 개념적으로 이해만 하고 있도록 하자).


2.4절. MIB에 대해서

SNMP는 네트워크를 관리하기 위한 프로토콜이다. 그렇다면 무엇을 관리할 것인가(관리객체)를 결정해야 할것이다. 관리객체를 결정했다면, 이러한 관리객체를 효과적으로 관리하기 위해서 이를 분류해야 할것이다. 이게 바로 MIB이다.

MIB는 Man In Black의 줄임말이 아니다. Management Information Base의 줄임말인데, 관리되어야할 자원 객체의 분류된 정보를 말한다. 관리되어야할 객체는 시스템정보, 네트워크사용량, 네트워크 인터페이스정보 등이 된다.

이 MIB객체들은 관리하기 편하도록 Tree구조를 가지게 된다. 다음은 MIB의 일반적인 구조이다.

그림 2. MIB계층 구조

MIB는 위에서 처럼 계층적인(디렉토리) 구조를 가지게 된다(위의 그림은 MIB를 설명하기 위해 일부만을 표시하고 있다). 예를들어서 agent가 설치되어 있는 시스템으로 부터 시스템부가정보(sysDescr)를 얻어오길 원한다면 ISO.org.dod.internet.mgmt.mib-2.system.sysDescr과 같은 식으로 manger에서 데이타를 요청하면 된다.

위의 MIB계층 구조를 보면 각 MIB옆에 숫자가 있는것을 볼수 있다. 이 숫자가 OID번호이다. 즉 sysDescr의 OID값은 1.3.6.1.1.2.1.1.1 이 될것이다. OID번호를 이용하는 이유는 MIB고유 문자열을 통해서 원하는 데이타를 가져오기위해서는 아무래도 요청이 길어질수가 있기 때문이다.

MIB는 IANA(Internet Assigned Number Authority)라는 단체에서 관리하며 표준적으로 사용되고 있다. 그럼으로 표준적인 MIB구현을 위해서는 IANA에서 OID를 부여받아야만 한다. 그래야 전체네트워크상에서 다른 여러가지 MIB와 중복되지 않고 사용이 가능할것이다.

작은 정보: cisco과 같은 대중적인(거의 표준이나 마찬가지인) 제품들은 모두 자체적인 MIB를 구현해서 IANA에 등록하여 사용하고 있다. 여러분이 cisco 라우터등의 SNMP정보를 접근할수 있다면 cisco MIB가 등록되어 있음을 확인할수 있을것이다. 확인하는 방법은 다음 강좌에서 따로 언급하도록 하겠다.

MIB는 계층적 구조를 가짐으로 필요에 따라서 확장해서 사용이 가능하며, (물론 프로그래밍 능력이 있어야 하지만)때에 따라서는 자체 회사내에서만 사용가능하거나 제한된 네트워크 영역의 네트워크상황을 관제하는 제품을 위한 MIB를 추가해야 하는경우가 생길수 있을것이다. 그래서 사설로 MIB를 만들어서 사용할수 있는 여지를 남겨두었다. (마치 독립된 지역네트워크를 위해 사설IP를 사용하는 것처럼) 이러한 사설 MIB는 private(4)의 enterprises(1)에 정의해서 사용할수 있다. 여러분이 그리 대중적이지 않은 그래서 IANA에 등록되지 않은 어떤 장비의 고유 SNMP정보를 얻어오고 싶다면 업체에 문의하거나, 메뉴얼을 확인하는 정도로 쉽게 SNMP정보를 얻어올수 있다.

현재 MIB는 버젼 2까지나와 있으며, 버젼의 구분을 위해서 MIB-1, MIB-2로 부르고 있다. MIB-2는 MIB-1의 확장판으로 MIB-1의 모든 객체를 포함하여 약 171개의 객체들을 더 포함하고 있다. 최근의 제품들은 대부분 MIB-2를 지원하고 있다. 물론 위에서 말했듯이 독자적인 MIB를 만들어서 사용할수 있으며, 이를 확장 MIB라고 부른다.


2.5절. SNMP 프로토콜의 동작과 구성

현재 SNMP는 버전 3가지 나와있는 상태이지만 아직까지는 버젼2가 가장 널리 사용 되고 있다. 필자역시 SNMP 버젼 2에 대한 경험이 많은 관계로 버젼2를 기준으로 설명하도록 하겠다.

SNMP는 기본적으로 네트워크 정보를 수집하는데 그 목적이 있는데, 수집하는 몇가지 각각 다른 방법이 있다. 일반적으로 생각해서 우리가 생활중에 얻게 되는 정보는 우리가 요구해서 발생하는 정보와(신문을 구입한다든지, 인터넷으로 서핑을 하는등) 뉴스속보와 같은 형식으로 중요한 일이 있을때 발생하는 정보가 있을것이다. 또한 단지 정보를 얻는데 그치지 않고 정보를 입력하기도 한다.

SNMP정보수집역시 기본적으로 위의 일상생활에서의 정보수집과 같은 방식으로 이루어진다. 이하 snmp manager은 manager로 snmp agent는 agent로 부르도록 한다.

GET

manager에서 agent로 특정 정보를 요청하기 위해서 사용한다.

GET NEXT

기본적으로는 GET과 같은일을 한다. 그러나 SNMP에서 각정보들은 계층적 구조로 관리된다. 위의 MIB계층 구조를 나타낸 이미지에서 우리는 system(1)계층밑에 있는 모든 정보를 가져오고 싶을 때가 있을것이다. 그럴경우 GET NEXT를 사용할수 있다.

SET

manager에서 agent로 특정 값을 설정하기 위해서 사용한다.

TRAP

agent에서 통보해야될 어떤 정보가 발생했을때(임계치를 넘는네트워크자원 사용등) manager에게 해당 상황을 알리기 위해서 사용한다. 위의 다른 요청들이 동기적 요청이라면 이것은 비동기적 사건을 알리기 위해서 사용되어진다.

SNMP프로토콜은 기본적으로 어떤 정보를 요청하는 메시지와 이에 대한 응답메시지로 이루어지며 다음과 같은 구조를 가지고 있다.

표 1. SNMP 메시지

Version Community name SNMP PDU
Version은 말이 필요없다. SNMP프로토콜의 버젼번호를 나타낸다. Community name은 메니저와 에이전트간의 관계를 나타내는데, 인증, 접근통제등의 목적으로 사용된다. 보통은 간단하게 public을 사용한다. PDU 는 Physical Data Unit의 줄임말인데, 실제 전송되는 필요한 정보들을 담고 있는 Unit이다. Unit 이라고 하는 이유는 실제 전송되는 정보들의 부가 속성을 나타내기 위한 몇가지 값들을 포함하고 있기 때문이다. PDU는 PDU 타입(GET인지 Set인지 GET Next인지, TRAP인지등)과, Request-id, 실제보내고자 하는 데이타등(OID와 OID에 대한 값들)으로 구성되어 있다.

SNMP를 통해서 전달되는 메시지들은 기본적으로 UDP를 이용하게 된다. 바로위에서 PDU는 Request-id를 포함하고 있다고 했는데, 데이타그램처리방식인 UDP의 단점을 극복하기 위해서 사용되는 값으로, 각 메시지의 요청번호를 표시한다. 그래야만 수신된 SNMP메시지가 어떤 요청에 대해서 수신된 메시지인지 확인이 가능할것이기 때문이다.


3절. SNMP 설치 및 운용

그럼 실제로 시스템에 SNMP(agent와 manager 애플리케이션)을 설치해서 정보를 가져오는걸 간단히 테스트 해보도록 하겠다.

설치는 Linux(Kernel-2.4.x)에서 ucd-snmp로 할것이다. 위에서 설명했듯이, SNMP는 manager과 agent로 운영되게 되는데, 테스트의 편의를 위해서 하나의 시스템(localhost)에서 manager와 agent를 운용하도록 하겠다.


3.1절. ucd-snmp 설치

ucd-snmp는 net-snmp.sourceforge.net에서 얻을수 있으며 애플리케이션 관련 정보들도 얻을수 있다. ucd-snmp는 현재 버젼 5.x대까지 진행되어 있는데, 5.x부터는 net-snmp로 이름을 바꾸고 개발되어지고 있으며, 4.x버젼까지를 ucd-snmp라고 부르고 있다. 필자는 익숙한 ucd-snmp(버젼 4.x)를 설치하도록 할것이다. 비록 net-snmp가 최신이긴 하지만 별로 다루어본적이 없고, 대부분의 경우 아직까지는 ucd-snmp가 많이 사용되어지고 있기 때문이다. 최신이 아니라고 불만을 가질 필요는 없다. 근본적으로 net-snmp와 ucd-snmp간의 차이는 없으며, 우리의 목적은 최신의 snmp애플리케이션을 테스트하는게 아닌 snmp의 기능과 원리를 이해하고 이를 이용해서 필요한 응용 애플리케이션을 작성하는 것이기 때문이다.

위의 URL에서 ucd-snmp를 다운받아서 압축을 풀고 컴파일 하도록 하자. 컴파일 하는중에는 아마도 아무런 문제가 없을것이다. 컴파일은 매우 일반적인 방법을 따른다. 적당한 디렉토리에 압축을 풀고 ./configure, make, make install 하면된다.

	
[root@localhost src]# tar -xvzf ucd-snmp-4.2.6.tar.gz 
[root@localhost src]# cd ucd-snmp-4.2.6 
[root@localhost ucd-snmp-4.2.6]# ./configure  
[root@localhost ucd-snmp-4.2.6]# make 
[root@localhost ucd-snmp-4.2.6]# make install
			
헤에... 너무 간단하지 않은가 ?


3.2절. SNMP AGENT 실행

make install 까지 했다면 agent와 manager프로그램이 모두 설치되어 있을 것이다. 그리고 여기에 더불어 개발자를 위한 각종 라이브러리와 헤더파일들도 설치된다. 이 라이브러리와 헤더파일들은 개발할때 필요하며 다음 강좌에서 다루게 될것이다.

ucd-snmp는 agent 프로그램으로 snmpd를 제공한다. agent환경을 제대로 만들려면 복잡해보이는(사실은 그리 복잡하다고 볼수없는) 설정파일을 만들어줘야 하지만 이것은 각자의 몫이다. net-snmp프로젝트 홈페이지에서 제공하는 메뉴얼을 참고하기 바란다. 어쨋든 현재로써는 단지 snmpd를 띄우는 정도로 snmp agent환경을 만들수 있다.

[root@localhost root]# snmpd
			
이것으로 snmp를 테스트할 최소한의 agent환경이 구축되었다.


3.3절. SNMP MANAGER 테스트

3.3.1절. 동기적인 데이타 요청 - snmp get, get next

GETGET NEXT는 동기적인 정보요청을 위해서 사용한다. manager에서 agent에 대해서 정보를 요청했을때 해당 정보를 agent에서 보내주는 방식이다. GET은 단일정보요청을 위해서 사용하며, GET NEXT는 해당 계층의 하위에 있는 모든 정보의 요청을 위해서 사용된다.

ucd-snmp는 이러한 정보요청을 위한 manager프로그램으로 snmpgetsnmpnext, snmpwalk를 제공한다.

snmpget은 이름에서 알수 있듯이 agent로부터 특정한 정보를 얻어내기 위해서 사용한다. 정보를 얻기 위해 필요한 기본정보는 agent가 설치되어 있는 서버의 주소(혹은 이름) 와 커뮤니티(권한을 위한)이름 그리고 얻기 원하는 정보의 OID번호 혹은 MIB의 계층이름이다. 예를들어서 localhost로부터 public권한을 가지고 sysDescr(시스템 부가정보)정보를 얻어오고 싶다면 아래와 같이 하면 된다.

 
[root@localhost /root]# snmpget localhost public system.sysDescr.0
system.sysDescr.0 = Linux localhost 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686
				
혹은 MIB이름대신에 OID번호를 사용해도 된다.
 
[root@localhost /root]# snmpget localhost public 1.1.0 
system.sysDescr.0 = Linux localhost 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686
				

snmpwalk는 해당 MIB의 하위계층에 있는 모든 정보를 요청한다. 예를들어 system MIB의 하위 계층에 있는 모든 OID에 대한 정보를 요청하길 원한다면 아래와 같이 하면된다. 이게 가능한 이유는 snmpwalk가 정보를 요청하기 위해서 snmp메시지를 만들때 PDU타입을 GET NEXT를 사용하기 때문이다. 나중에 직접구현하게 될것이다. 지금은 구현에 신경쓰지 말자.

[root@localhost /root]# snmpwalk localhost public system
system.sysDescr.0 = Linux localhost 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686
system.sysObjectID.0 = OID: enterprises.ucdavis.ucdSnmpAgent.linux
system.sysUpTime.0 = Timeticks: (2685699) 7:27:36.99
system.sysContact.0 = yundream@joinc.co.kr
system.sysName.0 = localhost
system.sysLocation.0 = myhome 
system.sysORLastChange.0 = Timeticks: (0) 0:00:00.00
....
				
system하위의 모든 OID에 대한 정보를 얻어오고 있음을 확인할수 있다.

snmpgetnext는 snmpwalk의 기능 축소판정도로 볼수 있을것이다. 즉 MIB계층구조에서 현재 요청한 OID의 다음 OID의 정보를 가져온다. 예를들어 system.sysDescr.0에 대한 정보를 요청하면 다음 OID인 system.sysObjectID.0의 정보를 요청하게 될것이다. 이게 가능한 이유는 snmpwalk와 마찬가지로 내부적으로 GET NEXT를 이용하고 있기 때문이다. snmpwalk가 더이상 얻을수 없을때까지 OID를 요청하는것과 달리 snmpgetnext 바로다음의 OID만을 요청한다.

 
[root@localhost /root]# snmpgetnext localhost public system system.sysDescr.0
system.sysDescr.0 = Linux localhost 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686
system.sysObjectID.0 = OID: enterprises.ucdavis.ucdSnmpAgent.linux
				


3.3.2절. 비동기적인 데이타 요청 - snmp trap

기본적으로 GET, GET NEXT를 통한 데이타요청은 일정한 polling시간을 가지고 manager에서 agent로 필요한 정보를 요청하는 방식이다. 그러나 이걸 이용해서는 비동기적으로 발생하는 정보를 수집할수가 없다.

이러한 비동기적인 정보는 여러가지가 될수 있다. 예를들면 특정 네트워크 세그먼트에 문제가 생겼다거나 디스크나 메모리용량을 과다하게 사용하고 있다거나(많은 운영체제의 경우 시스템정보까지도 snmp를 통해서 얻을수 있도록 허용하고 있다)하는 사건들은 비동기적으로 발생할것이다. 이럴경우에는 agent에서 manager측으로 사건을 통보해야 할것이다. 이렇게 agent에서 manager측으로 비동기적으로 사건을 통보하는 것을 SNMP TRAP라고 한다(간단히 말해서 경고메시지 보내는거다).

ucd-snmp에서는 이러한 trap정보를 전송하고 받기 위해서 snmptrapdsnmptrap를 제공한다. snmptrapd는 agent에 제공되는 데몬프로그램으로 manager에서의 trap데이타 발생을 기다린다. snmptrap는 agent에 설치되어서 사용될수 있으며 trap데이타를 manager로 전송하는 일을한다.

이 snmptrap은 꽤 유용하게 사용할수 있다. 간단하게 스크립트로 만들어서 어떤 파일이 변조되었을경우 trap정보를 manager쪽으로 발생시킨다거나, 프로세스 갯수가 일정갯수 이상 초과했을때 이를 전송한다든지 하는 기능을 비교적 간단하게 추가시킬수 있을것이다.

다음은 ucd-snmp에서 제공하는 trap애플리케이션을 이용한 간단한 테스트이다. 먼저 snmptrapd를 manager측에서 실행시켜야 한다. 이 애플리케이션은 옵션없이 실행할경우 데몬모드로 실행되며 표준출력을 시키지 않음으로 다음과 같이 옵션을 주고 실행시켜서 일반모드(forground)에서 받은 trap정보를 표준출력하도록 실행시키도록 하자.

[root@localhost root]# snmptrapd -f -P
2003-04-23 00:13:34 UCD-snmp version 4.2.6 Started.
				
이제 agent측에서 snmptrap를 이용해서 trap정보를 manager로 전송해보도록 하자.
[root@localhost root]# snmptrap -v 2c -c public localhost "" ucdStart sysContact.0 s "yundream"
				
그러면 manager로 system.sysContact.0="yundream" 과 같은 정보가 전달되는걸 확인할수 있을것이다.

이들 ucd-snmp에서 제공하는 애플리케이션들의 자세한 사용법은 메뉴얼 페이지를 참고하기 바란다.


4절. 결론

이상 SNMP의 개념과 개념의 이해를 위해서 실제 사용되는 snmp애플리케이션을 설치해서 간단히 운영테스트까지 해보았다. 이러한 운영테스트를 위해서 ucd-snmp를 사용했는데, 다음 강좌는 ucd-snmp에서 제공하는 snmplib를 통해서 snmp애플리케이션을 만드는 법을 다루도록 하겠다. 

Posted by 두장
2008. 12. 15. 11:46
#include <stdio.h>
#include <malloc.h>

typedef struct { int key; } element;
typedef struct tree_node* tree_pointer;
enum {FALSE, TRUE};
struct tree_node {
        tree_pointer left_child;
        element data;
        short int bf;
        tree_pointer right_child;
};

struct stack {
        tree_pointer data;
        struct stack* node;
};

struct stack* top =NULL;
int unbalanced = FALSE;
tree_pointer root = NULL;

int x = 40 , y = 1;                          // X , Y 좌표

void avl_insert(tree_pointer* , element, int*);
void left_rotation(tree_pointer* ,int* );
void right_rotation(tree_pointer* ,int* );
void push(tree_pointer );
tree_pointer pop();
bool isempty();
void avl_tree_print();

void main()
{
        element data;
        for(int i = 0 ; i< 12; i++)
        {
                scanf("%d",&data.key);
                avl_insert(&root,data,&unbalanced);
        }
        avl_tree_print();
        

}

void avl_insert(tree_pointer* parent, element x, int* unbalanced)
{
        if(!*parent)
        {
                *unbalanced = TRUE;
                *parent = (tree_pointer)malloc(sizeof(tree_node));
                
                (*parent)->left_child = (*parent)->right_child = NULL;
                (*parent)->data = x;
                (*parent)->bf = 0;
        }
        else if(x.key < (*parent)->data.key)
        {
                avl_insert(&(*parent)->left_child,x,unbalanced);
                if(*unbalanced)
                {
                        switch((*parent)->bf)
                        {
                        case -1:
                                (*parent)->bf = 0;
                                break;
                        case 0:
                                (*parent)->bf = 1;
                                break;
                        case 1:
                                left_rotation(parent,unbalanced);
                                
                        }
                }
        }
        else if(x.key > (*parent)->data.key)
        {
                avl_insert(&(*parent)->right_child,x,unbalanced);
                if(*unbalanced)
                {
                        switch((*parent)->bf)
                        {
                        case 1:
                                (*parent)->bf = 0;
                                break;
                        case 0:
                                (*parent)->bf = -1;
                                break;
                        case -1:
                                right_rotation(parent,unbalanced);
                        }
                }
        }
        else
        {
                *unbalanced = FALSE;
                printf("The key is already in the tree");
        }
}

void left_rotation(tree_pointer* parent,int* unbalanced)
{
        tree_pointer grand_child, child;
        child = (*parent)->left_child;
        if(child->bf == 1)                  // LL 회전
        {
                (*parent)->right_child = child->right_child;
                child->right_child = *parent;
                (*parent)->bf = 0;
                *parent = child;                
        }
        else                               // LR 회전
        {
                grand_child = child->right_child;
                child->right_child = grand_child->left_child;
                grand_child->left_child = child;
                (*parent)->left_child = grand_child->right_child;
                grand_child->right_child = *parent;
                switch(grand_child->bf)
                {
                case 1:
                        (*parent)->bf = -1;
                        child->bf = 0;
                        break;
                case 0:
                        (*parent)->bf = child->bf = 0;
                        break;
                case -1:
                        (*parent)->bf = 0;
                        child->bf = 1;
                }
                *parent = grand_child;
        }
        (*parent)->bf = 0;
        *unbalanced = FALSE;
}




void right_rotation(tree_pointer* parent ,int* unbalanced)
{
        tree_pointer grand_child, child;
        child = (*parent)->right_child;
        if((*parent)->bf == -1 )             // RR 회전
        {
                (*parent)->right_child = child->left_child;
                child->left_child = *parent;
                (*parent)->bf = 0;
                (*parent) = child;
        }
        else                                 // RL회전
        {
                grand_child = child->left_child;
                child->left_child = grand_child->right_child;
                grand_child->right_child = child;
                (*parent)->right_child = grand_child->left_child;
                grand_child->left_child = (*parent);
                switch(grand_child->bf)
                {
                case -1:
                        (*parent)->bf = 1;
                        child->bf = 0;
                        break;
                case 0:
                        (*parent)->bf = child->bf = 0;
                        break;
                case 1:
                        (*parent)->bf = 0;
                        child->bf = -1;
                }
                (*parent) = grand_child;
        }
        (*parent)->bf = 0;
        *unbalanced = FALSE;
}

void push(tree_pointer data)
{
        if(top == NULL)
        {
                top = (struct stack*)malloc(sizeof(struct stack));
                top->data = data;
                top->node = NULL;
        }
        else
        {
                struct stack* new_node;
                new_node = (struct stack*)malloc(sizeof(struct stack));
                new_node->data = data;
                new_node->node = top;
                top = new_node;
        }
}

tree_pointer pop()
{
                tree_pointer data;
                struct stack* del_node;
                data = top->data;
                del_node = top;
                top = top->node;
                delete del_node;

                return data;
}
                
bool isempty()
{
        if(top == NULL)
                return TRUE;
        else
                return FALSE;
}

void avl_tree_print()
{
        tree_pointer current_node;
        current_node = root;
        bool done = TRUE;

        do
        {
                while(current_node != NULL)
                {
                        push(current_node);
                        current_node =current_node->left_child;
                        x-= 5;                // X 좌표 감소 좌측으로 이동
                        y+= 1;                // Y 좌표 증가 아래로 이동
                }

                if(top != NULL)
                {
                        current_node = pop();
                        y--;                  // Y 좌표 감소 위쪽으로 이동
                        
                        printf("%d",current_node->data.key);
                        current_node = current_node->right_child;

                        if(current_node != NULL)
                        {
                                x+=5;
                                y+=1;
                        }
                }
                else
                        done = FALSE;
        } while(done);

}
Posted by 두장
이전버튼 1 2 3 4 5 6 7 8 ··· 15 이전버튼