20140623 (pthread, 레코드 잠금)

86일차







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

pthread

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



--- pthread 관련 함수



출처 : http://www.joinc.co.kr/modules/moniwiki/wiki.php/Site/Thread/Beginning/PthreadApiReference


Pthread API Reference

윤 상배

고친 과정
고침 0.92004년 6월 30일 12시
pthread 취소관련 api 추가
고침 0.82003년 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_join.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_join 을 호출하지 않더라도 즉시 모든 자원이 해제(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_join(&p_thread, (void **)&status);
}
			



3.2. pthread_mutex_destroy


int pthread_mutex_destroy(pthread_mutex_t *mutex);
			

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

pthread_mutex_destroy 를 이용해서 제대로 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. 제주지사 직원이 터미널을 이용해 예약 프로그램을 실행시킨다. 이를 프로세스 A라 하자.

2. 제주공항 직원이 터미널을 이용해 예약 프로그램을 실행시킨다. 이를 프로세스 B라 하자.

3. 프로세스 A가 KE1004기에 빈 좌석이 한 개 있음을 확인한다.

4. 프로세스 B가 KE1004기에 빈 좌석이 한 개 있음을 확인한다.

5. A가 예약을 했다.

6. B도 예약을 했다.


자리의 주인은 누구인가?


위와 같이 한 공통된 자원을 동시에 사용할 경우 이런 오류들의 발생을 방지하기 위해

레코드들을 잠근다.





--- fcntl

파일 제어와 관리 돼있다.


#include <unistd.h>

#include <fcntl.h>


int fcntl(int fd, int cmd);

int fcntl(int fd, int cmd, long arg);

int fcntl(int fd, int cmd, struct flock *lock);


fd : 파일 식별자

cmd : 동작 지정

F_DUPFD    : 새로운 파일 식별자를 부여하고 이를 반환한다.

F_GETFD    : close-on-exec 플래그를 얻는다.

F_SETFD    : close-on-exec 플래그를 설정한다.

F_GETFL    : 파일 상태 플래그를 얻는다.

F_SETFL    : 파일 상태 플래그를 설정한다.

F_GETLK    : 레코드 잠금 정보를 얻는다.

F_SETLK    : 레코드 잠금 정보를 설정한다.

F_SETLKW : 레코드 잠금을 설정하는데 다른 프로세스가 사용 중이라면 끝날 때까지 기다린다.


arg : cmd에 따라 달라짐

lock : 레코드 잠금에서 사용되는 인수


struct flock

{

short l_type;        // 잠금유형

short l_whence;  // 영역을 정하기 위한 기준

off_t l_start;         // 영역의 시작 위치

off_t l_len;           // 영역의 길이로 바이트 단위, 0이면 파일의 끝까지

off_t l_pid;           // F_GETLK일 때 레코드 잠금을 설정하고 있는 프로세스 ID

};



리턴값

성공시

cmd에 따라 달라짐


실패시

-1





파일 속성 관련 예제)


      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <fcntl.h>
      4 #include <sys/types.h>
      5 #include <unistd.h>
      6
      7 int filestaus(int fd, char *file);
      8
      9 int main(int argc, char **argv)
     10 {
     11     int fd;
     12     int newdf;
     13     int mode;
     14
     15     mode = atoi(argv[2]);
     16     if(-1 == (fd = open(argv[1], mode)))
     17     {
     18         perror("open failed");
     19         exit(1);
     20     }
     21
     22     if(-1 == (filestatus(fd, argv[1])))
     23     {
     24         exit(2);
     25     }
     26
     27     close(fd);
     28
     29     exit(0);
     30 }
     31
     32
     33 int filestatus(int fd, char *file)
     34 {
     35     int flag;
     36
     37     if(-1 == (flag = fcntl(fd, F_GETFL)))
     38     {
     39         perror("fcntl failed");
     40         return -1;
     41     }
     42
     43     switch(flag & O_ACCMODE)
     44     {
     45         case O_RDONLY:
     46             printf("%s is read only file\n", file);
     47             break;
     48         case O_WRONLY:
     49             printf("%s is write only file\n", file);
     50             break;
     51         case O_RDWR:
     52             printf("%s is read-write file\n", file);
     53             break;
     54         default:
     55             printf("no such mode\n");
     56     }
     57
     58     return 0;
     59 }
     60




결과



0은 읽기, 1은 쓰기, 2는 읽기 쓰기 모드로 파일을 열었다.







레코드 잠금 예제)


exe3 프로그램 소스


      1 #include <stdio.h>
      2 #include <string.h>
      3 #include <unistd.h>
      4 #include <fcntl.h>
      5
      6 int main(int argc, char **argv)
      7 {
      8     int fd;
      9     struct flock filelock;
     10
     11     filelock.l_type = (!strcmp(argv[1], "r"))? F_RDLCK:F_WRLCK;
     12
     13     filelock.l_whence = SEEK_SET;
     14     filelock.l_start = 0;
     15     filelock.l_len = 0;
     16
     17     fd = open(argv[2], O_RDWR | O_CREAT, 0666);
     18
     19     if(fcntl(fd, F_SETLK, &filelock) == -1)
     20     {
     21         perror("fcntl failed");
     22         exit(1);
     23     }
     24
     25     printf("locked %s\n", argv[2]);
     26     sleep(30);
     27     printf("unlocked %s\n", argv[2]);
     28
     29     return 0;
     30 }
     31





exe4 프로그램 소스


      1 #include <stdio.h>
      2 #include <string.h>
      3 #include <unistd.h>
      4 #include <fcntl.h>
      5
      6 int main(int argc, char **argv)
      7 {
      8     int fd;
      9     struct flock filelock;
     10
     11     filelock.l_type = (!strcmp(argv[1], "r"))? F_RDLCK:F_WRLCK;
     12
     13     filelock.l_whence = SEEK_SET;
     14     filelock.l_start = 0;
     15     filelock.l_len = 0;
     16
     17     fd = open(argv[2], O_RDWR);
     18
     19     if(fcntl(fd, F_SETLK, &filelock) == -1)
     20     {
     21         perror("fcntl failed");
     22         exit(1);
     23     }
     24
     25     printf("success\n");
     26
     27     return 0;
     28 }
     29



결과




설정

트랙백

댓글

20140620 (시그널, 멀티쓰레드)

85일차












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

시그널

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





--- 시그널

프로그램에게 신호를 보내 프로그램이 그에 따라 종료되거나

어떤 처리를 하게 되는데 이 신호를 시그널이라 한다. (신호가 영어로 시그널인데..)




-- 시그널 종류





1. SIGHUP(HUP) : 연결 끊기. 프로세스의 설정파일을 다시 읽는데 사용된다.

2. SIGINT(INT) : 인터럽트

3. SIGQUIOT(QUIT) : 종료

4. SIGILL(ILL) : 잘못된 명령

5. SIGTRAP(TRAP) : 트렙 추적

6. SIGIOT(IOT) : IOT 명령

7. SIGBUS(BUS) : 버스 에러

8. SIGFPE(FPE) : 고정 소수점 예외

9. SIGKILL(KILL) : 죽이기. 이 시그널은 잡히지 않는다.

10. SIGUSR1(USR1) : 사용자 정의 시그널 1

11. SIGSEGV(SEGV) : 세그멘테이션 위반

12. SIGUSR2(USR2) : 사용자 정의 시그널 2

13. SIGPIPE(PIPE) : 읽을 것이 없는 파이프에 대한 시그널

14. SIGALRM(ALRM) : 경고 클럭

15. SIGTERM(TERM) : 소프트웨어 종료 시그널, 일반적으로 kill 시그널이 전송되기 전에 전송된다.

                            잡히는 시그널이기 때문에 종료되는 것을 트랙할 수 있다.

16. SIGKFLT : 코프로세서 스택 실패

17. SIGCHLD(CHLD) : 자식 프로세스의 상태변화

18. SIGCONT(CONT) : STOP 시그널 이후 계속 진행할 때 사용

19. SIGSTOP(STOP) : 정지. 이 시그널 역시 잡을 수 없다.

20. SIGTSTP(TSTP) : 키보드에 의해 발생하는 시그널로 Ctrl+Z로 생성된다.








--- 시그널 집합

시그널 집합은 말 그대로 시그널을 원소로 하는 집합을 의미한다.

위에 시그널 종류들이 하나의 원소들이다.






--- sigemptyset

빈 시그널 집합을 생성


#include <signal.h>


int sigemptyset(sigset_t *set);


set : 생성하고자 하는 시그널 집합



리턴값

성공시 

0


실패시

-1



--- sigfillset

모든 시그널을 원소로 하는 시그널 집합을 생성.


#include <signal.h>


int sigfillset(sigset_t *set);


set : 생성하고자 하는 시그널 집합



리턴값

성공시

0


실패시

-1




--- sigaddset

시그널을 시그널 집합에 추가한다.


#include <signal.h>


int sigaddset(sigset_t *set, int signum);


set : 시그널 집합

signum : 추가하고자 하는 시그널 번호



리턴값

성공시

0


실패시

-1




--- sigdelset

시그널 집합에서 시그널을 삭제


#include <signal.h>


set : 시그널 집합

signum : 삭제하고자 하는 시그널 번호



리턴값

성공시

0


실패시

-1




--- sigismember

시그널이 시그널 집합에 속하는지 확인


#include <signal.h>


int sigismember(const sigset_t *set, int signum);


set : 시그널 집합

signum : 확인하고자 하는 시그널 번호



리턴값

성공시

속하면 1

속하지 않으면 0


실패시

-1






--- sleep

일정 시간 동안 정지


#include <unistd.h>


unsigned int sleep(unsigned int seconds);


seconds : 정저할 시간으로 단위는 초



리턴값

성공 : 남은 시간





--- signal

시그널 처리를 설정


#include <signal.h>


void (*signal(int signum, void (*sighandler)(int)) )(int);


signum : 시그널 번호

sighandler : 설정할 시그널 핸들러

SIG_IGN : 시그널을 받으면 무시한다.

SIG_DFL : 시그널을 받으면 시스템에서 기본적으로 설정한 동작을 한다.

함수 이름 : 시그널을 받으면 '함수 이름' 함수가 실행 된다.



리턴값

성공시

이전의 시그널 핸들러 포인터


실패시

-1 (SIG_ERR)








소스)



결과




처음 Ctrl + C 누르면 signalHandler() 함수가 실행어서 원래 기능을 다시 하게 되어

두번째 Ctrl + C 누르면 종료된다.














--- thread


멀티프로세스는 프로세스를 똑같이 메모리영역을 다 복사하여 실행하는 반면

멀티쓰레드는 실행될 함수단위로 복사한다.


멀티프로세스는 비용이 커지지만 부모 프로세스가 종료되어도 자식프로세스에 실행에는

큰 영향을 끼치지 않는다.

멀티쓰레드는 비용은 작아지지만 부모 프로세스가 종료될식 자식프로세스도 모두 종료된다.





--- pthread_create

쓰레드를 생성한다.


#include <pthread.h>


int pthread_create(pthread_t  *  thread, pthread_attr_t *

                   attr, void * (*start_routine)(void *), void * arg);


thread : 쓰레드 ID

attr : 속성, 주로 NULL

start_routine : 실행시킬 함수

arg : 함수 인수



리턴값

성공시

0


실패시

에러번호




예제 소스)


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

volatile unsigned int uiCnt;

void * smart1(void *);
void * smart2(void *);
void * smart3(void *);

int main()
{
        pthread_t thread1;
        pthread_t thread2;
        pthread_t thread3;

        pthread_create(&thread1, 0, smart1, 0);
        pthread_create(&thread2, 0, smart2, 0);
        pthread_create(&thread3, 0, smart3, 0);

        getchar();

        return 0;
}


void * smart1(void * vpData)
{
        while(1)
        {
                printf("1 : %u\n", uiCnt);
                sleep(1);
                ++uiCnt;
        }

        return ;
}

void * smart2(void * vpData)
{
        while(2)
        {
                printf("2 : %u\n", uiCnt);
                sleep(2);
                ++uiCnt;
        }

        return ;
}

void * smart3(void * vpData)
{
        while(3)
        {
                printf("3 : %u\n", uiCnt);
                sleep(3);
                ++uiCnt;
        }

        return ;
}




컴파일시 라이브러리를 추가해 줘야한다.

컴파일때 라이브러리 추가 명령은 gcc -o * *.c -lpthread




설정

트랙백

댓글

20140619 (프로세스, 함수 포인터)

84일차








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

프로세스

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



--- exit

프로세스를 정상적으로 종료시키는 함수.


#include <stdlib.h>


void exit(int status);


status : main 함수의 리턴값



리턴값

없음





--- atexit

프로그램을 정상적으로 종료할 때 실행될 함수를 등록한다.


#include <stdlib.h>


int atexit( void (*function)(void) );


function : 등록할 함수 이름으로 인수르 값는 함수



리턴값

성공시

0

실패시

-1




--- wait

자식 프로세스가 종료될 때까지 기다린다.


#include <sys/types.h>

#include <sys/wait.h>


pid_t wait(int *status);


status : 자식 프로세스가 종료될 때의 상태 정보.


status 4byte 중에 하위 2byte만 사용하는데

정상 종료시

하위 2byte 중 상위 1byte에 return 값, exit(값)이 들어가고

하위에는 0이 채워짐.


비정상 종료시

최하위 1byte에 프로세스를 종료시킨 시그널 번호가 저장된다.



리턴값

성공시

종료된 자식 프로세스의 프로세스 ID


실패시

-1








--- waitpid

(특정) 자식 프로세스가 종료될 때까지 기다린다.


#include <sys/types.h>

#include <sys/wait.h>


pid_t waitpid(pid_t pid, int *status, int options);


pid : 종료를 기다리는 자식 프로세스의 ID

-1을 주면 여러 자식중 하나라도 종료되기를 기다린다.

0을 주면 호출한 프로세스의 그룹 ID와 같은 그룹 ID를 가지는 자식 프로세스가 종료되기를 기다린다.

양수를 주면 그에 해당하는 자식 프로세스가 종료되기를 기다린다.


status : 자식 프로세스가 종료될 때의 상태 정보

options : 옵션

WNOHANG 옵션을 주면 자식이 종료되지 않더라도 부모 프로세스는 계속 실행한다.

0 옵션을 주면 wait과 동일하게 자식이 종료할 때가지 부모는 블록화된다.



리턴값

성공시

종료된 자식 PID


실패시

-1








--- 프로세스를 대체하는 함수들

하나의 프로세스가 실행 중에 이 함수를 사용하여 다른 프로세스를 실행시키면

다른 프로세스를 실행시키고 자신은 종료한다.


#include <unistd.h>


int execl( const char *path, const char *arg, ... , NULL);

int execlp( const char *file, const char *arg, ... , NULL);

int execle( const char *path, const char *arg , ... , char * const envp[]);

int execv( const char *path, char *const argv[]);

int execvp( const char *file, char *const argv[]);

int execve( const char *path, char *const argv[], char *const envp[]);


path : 실행될 파일의 경로와 이름

file : 실행될 파일 이름

arg : 실행될 인수들

argv : 실행 파일의 인수 배열

envp : 환경변수



리턴값

성공시

없음


실패시

-1



 1 #include <stdio.h>

 2 #include <unistd.h>

 3

 4 int main()

 5 {

 6     printf("Running ls with execl\n");

 7

 8     if(-1 == (execl("/bin/ls", "ls", "-al", NULL)))

 9     {

10         printf("error\n");

11         return 0;

12     }

13

14     printf("execl failed to run ls\n");

15

16     return 0;

17 }














--------------
함수 포인터
--------------




--- 인자로 함수를 받을 때


void test1 ( int (*fp)(const char *, ... ) )

{

fp("test\n");

}


         : 인자 데이터 타입

fp : 인자명






--- 리턴값을 함수로 할 때


int (* test2 (void) )(const char *, ... )

{

return printf;

}


         : 리턴 타입






--- 인자도 받고 리턴값도 함수


int (* test3 (int (*fp)(const char *, ... )) )(const char *, ...)

{

return fp;

}


         : 인자 데이터 타입

fp : 인자명

         : 리턴 타입






int main()

{

int (*fp)(const char *, ... );


test1(printf);


fp = test2;

fp("test2\n");


fp = test3(printf);

fp("test3\n");


return 0;

}








설정

트랙백

댓글

20140618 (3 Hand shake, 4 way Hand shake, 프로세스)

83일차












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

Network 이론

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




--- 3 Hand Shake




1. SYN Segment(Client의 Synchronization Information 전송): Client는 Source Port에 자신을 나타내는 Port Number를 넣고, Destination Port에는 Server를 가리키는 Port Number를 넣는다. Sequence Number에 Client의 ISN(Initialization Sequence Number)를 넣고, Acknowledgment Number는 0을 넣고, Flag는 SYN bit를 1로 설정하여 전송한다.

2. SYN+ACK Segment(Server의 Synchronization Information 전송 + SYN Segment 수신 확인): Server는 Source Port에 자신의 Port Number를 넣고, Destination Port에는 Sender의 Port Number를 넣는다. Sequence Number에는Server의 ISN를 넣고, Acknowledgment Number에는 “Client의 ISN + 1”의 값을 넣고, Flag는 SYN 와 ACK bit를 모두 1로 설정하여 전송한다.

3. ACK Segment(SYN Segment 수신 확인): Client는 첫번째 단계와 동일하게 Source Port와 Destination Port를 설정하고, Acknowledgment Number에는 “Server의 ISN + 1”의 값을 넣고, Flag는 ACK bit를 1로 설정하여 전송한다.



다시 요약하면

1. Seq 수를 random으로 생성,

    SYN Flag set하고 보냄.


2. Ack를 Seq + 1로 하고,

    Seq를 random으로 생성,

    SYN, ACK Flag set하여 보냄.


3. Ack를 Seq + 1로 하고,

    Seq를 Ack로 하고,

    ACK Flag set하여 보냄.



문제점.

- 서로 보낸 Seq, Ack 번호가 다를 경우 뭔가 잘못되었기 때문에 통신이 이루어지지 않는다.

- client가 1번만 보내고 대기하게 되면 서버는 몇 초간 대기한다.

   엄청나게 많은 1번이 오면 서버가 멈춘다.

   (AF_RAW 옵션으로 ip spoofing으로 DOS 공격이 이런 경우이다.)









--- 4 way Hand Shake



3 hand shake는 TCP 통신을 시작할 때 절차이고,

4 way hand shake는 TCP 통신을 종료할 때 절차이다.



















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

프로세스

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



--- 실행중에 있는 프로그램을 프로세스(Process)라고 부른다


Windows 경우 작업관리자

Linux 경우 ps 명령어로

현재 실행 중인 프로세스 리스트를 볼 수 있다.




--- fork


자식 프로세스를 생성한다.


#include <sys/types.h>

#include <unistd.h>


pid_t fork(void);



리턴값

성공시

부모 프로세스 : 자식 프로세스의 프로세스 ID

자식 프로세스 : 0


실패시

-1





예제)





One 이 한 번 출력되고 Two가 두번 출력되었다.


9번 줄에 fork()함수가 호출되면서 프로세스가 두 개가 되었다.

그래서 그 아래 소스는 두번씩 실행된다.






--- getpid, getppid


getpid 는 자신의 PID

getppid 는 부모 프로세스의 PID를 얻음.


#include <unistd.h


pid_t getpid(void);

pid_t getppid(void);



리턴값

성공시

PID


실패시

발생하지 않음







--- vfork

fork와 유사한데 다른점은 자식이 끝내기 전에 부모는 실행을 멈춘다.


#include <sys/types.h>

#include <unistd.h>


pid_t vfork(void);



리턴값

성공시

부모 프로세스 : 자식 프로세스의 프로세스 ID

자식 프로세스 : 0


실패시

-1





예제)








설정

트랙백

댓글

20140616 (TCP/IP client, server 소스)

81일차












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

TCP/IP 소켓 프로그래밍 client, server

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




--- client 소스 흐름


socket()

connect()

close()





- server 소스 흐름


socket()

bind()

listen()

accpet()

close()






--- bind

소켓에 저장된 소켓 주소를 넣는다.


#include <sys/types.h>

#include <sys/socket.h>


int  bind(int  sockfd, struct sockaddr *my_addr, socklen_t addrlen);


sockfd : 소켓 번호

my_addr : 소켓 주소 정보

addrlen : 소켓 주소 정보 길이


리턴값 : 성공 시 0, 실패 시 -1 반환되며 errno에 적당한 값이 설정됨.





--- errno

Error Number라는 뜻으로. 오류시 오류의 원인과 관련된 번호가 설정된다.


#include <errno.h>


extern int errno;  // 전역변수가 선언되어 있음





-- perror

errno 관련 함수


#include <stdio.h>

#include <errno.h>


void perror(const char *s);





--- listen
소켓에서 연결들을 기다린다.

#include <sys/socket.h>

int listen(int s, int backlog);


s : 소켓번호

backlog : 보통 5를 줌.


대충 요런 역할이다.






--- accept

소켓에 연결을 허용한다.


#include <sys/types.h>

#include <sys/socket.h>


int accept(int   s,  struct  sockaddr  *addr,  socklen_t *addrlen);


s : 소켓번호

addr : client 주소 정보 저장할 곳

addrlen : 정보 크기



리턴 값 : 연결된 client 소켓 번호가 리턴됨. 에러 시 -1.











--- 예제 소스


- client.c


#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <errno.h>


int main()
{
  int iSock;
  int iRet;
  struct sockaddr_in stAddr;

// 소켓 생성
  iSock = socket(AF_INET, SOCK_STREAM, 0);
  if(-1 == iSock)
  {
    perror("socket() ");
    return -10;
  }

// 소켓 정보 부여
  stAddr.sin_family = AF_INET;
  stAddr.sin_port = htons(PORT);
  stAddr.sin_addr.s_addr = inet_addr("192.168.10.250");

// 연결
  iRet = connect(iSock, (struct sockaddr *)&stAddr, sizeof(stAddr));
  if(-1 == iRet)
  {
    perror("connect() ");
    close(iSock);
    return -11;
  }

// 내용 전송
  printf("iSock : %d\n", iSock);


  close(iSock);
  
  return 0;
}




- server.c


#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <errno.h>


int main()
{
  int iSock;
  int iClient1;
  int iRet;
  unsigned int uiSize;
  struct sockaddr_in stAddr;
  struct sockaddr_in stClient1;

// 소켓 생성
  iSock = socket(AF_INET, SOCK_STREAM, 0);
  if(-1 == iSock)
  {
    perror("socket() ");
    return -10;
  }

// 주소 부여
  stAddr.sin_family = AF_INET;
  stAddr.sin_port = htons(PORT);
  stAddr.sin_addr.s_addr = inet_addr("192.168.10.250");

// 소켓에 주소 설정
  iRet = bind(iSock, (struct sockaddr *)&stAddr, sizeof(stAddr));
  if(-1 == iRet)
  {
    perror("bind() ");
    close(iSock);
    return -11;
  }

// 연결 대기
  iRet = listen(iSock, 5);
  if(-1 == iRet)
  {
    perror("listen() ");
    close(iSock);
    return -12;
  }

// 연결 허용
  uiSize = sizeof(stClient1);
  iClient1 = accept(iSock, (struct sockaddr *)&stClient1, &uiSize);
  if(-1 == iClient1)
  {
    perror("accept() ");
    close(iSock);
    return -13;
  }

// 내용
  printf("iSock : %d\n", iSock);
  printf("iClient1 : %d\n", iClient1);


  close(iSock);
  close(iClient1);
  
  return 0;
}


설정

트랙백

댓글

20140613 (TCP/IP)

80일차









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

TCP/IP 소켓 프로그래밍 기본 정보

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



--- 소켓 프로그래밍

우선 소스를 보자.


// 소켓 생성

iSock = socket(AF_INET, SOCK_STREAM, 0);

if(-1 == iSock)

{

    fprintf(stderr, "소켓을 생성할 수 없습니다.\n");

    return 10;

}


// 소켓 정보 부여

     stAddr.sin_family = AF_INET;

     stAddr.sin_port = htons(4016);

     stAddr.sin_addr = inet_addr("192.168.10.250");








--- socket 함수


-- 원형

#include <sys/types.h>

#include <sys/socket.h>


int socket(int domain, int type, int protocol);





-- description

- domain

AF_UNIX, AF_LOCAL    : 한 시스템 내에 프로그램끼리 통신. 로컬 통신

AF_INET                     : IPv4 인터넷 프로토콜

AF_INET6                    : IPv6 인터넷 프로토콜

.... (등등 몇 가지 더 있음)



- type

SOCK_STREAM    : TCP 통신

SOCK_DGRAM    : UDP 통신

SOCK_RAW    : 저수준에서 프로토콜을 직접 다룰 때 사용

... (등등 몇 가지 더 있음)


- protocol

보통 0을 넣음.

(몇 가지 옵션으로 사용한다는데 아직 뭔지는 잘 모르겠다.)






-- 리턴값

- 성공시 0보다 큰 정수 값.

- 소켓지정번호, socket descriptor라고 부른다.

- 소켓을 지시하여 소켓을 제어한다.





-- sockaddr_in

ip, port 정보를 저장하고 있는 구조체

위치 : /usr/include/linux/in.h


struct sockaddr_in

{

   sa_family_t       sin_family; /* Address family       */

   unsigned short int    sin_port;   /* Port number          */

   struct in_addr    sin_addr;   /* Internet address     */


   /* Pad to size of `struct sockaddr'. */

   unsigned char     __pad[      // 배열

__SOCK_SIZE__

-  sizeof(short int)

sizeof(unsigned short int)

- sizeof(struct in_addr)

];

/* 예전에 이 구조체를 개발할 때 이것 저것 시험용으로 사용하던 것인데

개발이 끝난 후 삭제해야하지 않아서 남은 부분으로 지금은 사용하지 않지만,

이 구조체를 사용할 때 이 영역까지 크기를 계산하여 사용한다.

무슨 파일 제어 관련 구조체가 있는데 이 구조체와 비슷하게 사용하려고???

크기를 같게 만들었다고 한다... 정확히는 모르겠고...

그래서 그 크기만큼 만들어 줘야해서 이 영역은 0으로 채워준다.

*/

};



typedef uint32_t in_addr_t;

struct in_addr

{

in_addr_t s_addr;

};








--- 컴퓨터의 칩에 따라 Big Endian과 Little Endian 이 있는데

통신을 할 때에 칩에 따라 정보가 다르게 해석될 수 있기 때문에

네트워크 통신은 Big Endian으로 통일한다.


--- htons

#include <netinet/in.h>


uint16_t htons(uint16_t hostshort);


호스트 숫자를 네트워크 숫자 형태로 바꿔준다.

Little Endian을 Big Endian으로 바꿔준다는 뜻이다.




--- inet_addr

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>


unsigned long int inet_addr(const char *cp);


"192.168.0.1" 처럼 문자형식 ip 주소를 숫자형식으로 변형해 준다.















설정

트랙백

댓글

20140612 (초음파 모듈 Ultra Sonic)

79일차









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

ATMega2560 Ultra Sonic(초음파 모듈)

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




--- ATMega128 소스를 그대로 가져왔다.

몇몇 값이 달라서 그것만 수정 후 실행해 보았다.


- 수정한 것들


- usonic.h







- usonic.c






요것만 바꾸고 실행하면 된다.



그런데 ATMega128 때에도 거리 측정 값이 좀 이상했다.

눈 대중으로만 봐도 20Cm 는 넘는 거리인데, 10Cm 밖에 안나온다는 것...

ATMega128 때는 그냥 넘어갔지만 이번엔 자세히 살펴보았다.


여러 Reg 를 살펴보고 값도 바꾸어 보았지만

알수가 없었는데 normal mode는 이중 버퍼가 안된다는 것을 대충 보았다.

뭔말인지 이해할 수 없었지만 혹시 TCNT0 값에 문제가 있는가 보았다.


usonic.c 소스에 아래 코드를 추가하여 값을 확인해 본 결과,



TCNT0 값이 118을 넘겼을 때에 0으로 초기화되는지 알았는데

118에 도달했을때? 인터럽트만 발생시키고 계속 값이 차올랐다.

그러니 비교방식을 써봐야 별 의미가 없었다.


그래서 0으로 초기화 시켜주는 소스를 추가하고 나니

그나마 근사치?로 나왔다.




'부산IT학원 > 스마트컨트롤러' 카테고리의 다른 글

20140616 (TCP/IP client, server 소스)  (0) 2014.06.16
20140613 (TCP/IP)  (0) 2014.06.13
20140611 (Network)  (0) 2014.06.11
20140610 (TWI로 외부 EEPROM과 통신)  (0) 2014.06.10
20140609 (TWI)  (0) 2014.06.10

설정

트랙백

댓글

20140611 (Network)

78일차







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

Network

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









IPv4 

4byte(32비트)체제로 운용된다.

다시말해 4바이트는 0 ~ 약40억 정도의 가지수를 갖고있다.

전세계 인구는 현재 

인구수에비해 주소보급이 모자람

ip공유기를 이용하기 시작



IPv6

IPv6이 시행중에 있음

곧있으면 상용화 예정

16byte(128비트)체제로 운용됨

이것을 시도함으로써 아이피의 고갈을 피할 수 있음





도메인 - 문자열로된 컴퓨터 이름을 말한다.

예를들어 google.com 이런걸 우리는 주소라고 하지만 

엄밀히 하자면 DNS name이다.


DNS(Domain Name Service)



실제로 아래 그림과 같이 DOS창에서 아래와 같은 커맨드를 입력해본다

필자는 www.naver.com 을 입력했다

그랬더니 아이피 주소가 나오는데 저걸 복사해서

인터넷 브라우저의 주소차에 갖다 붙혀보면 그에 해당하는 페이지가 뜬다.




네이버의 실제 주소는 위와 같은 IP주소이다.

우리가 알고있는 도메인은 진짜주소가 아니며

우리가 www.naver.com 를 주소창에 입력할 때 마다

DNS server가 125.209.222.142로 바꾸어 주었기 때문에 우리는 네이버와 같은 페이지에 접속이 가능한 것이다.





TCP방식

TCP방식의 기본적인 방식은 다음 그림과 같이

three hand shake방식으로 한다.


Apple|iPhone 4S|Normal program|Spot|1/24sec|F/2.4|4.3mm|ISO-50|Flash did not fire, auto mode|2014:06:11 16:13:12

위의 숫자 1~3까지의 순서를 무조건 거치는 방식이 TCP방식이다

3번까지의 순서를 마치면 비로소 데이터는 전송되며

프로세스B가 A에게 데이터를 받을 준비가 되지 않았을때에는

A는 기다렸다가 나중에 다시보낸다.


그로므로 이 방식은 속도가 느리지만 정확하다

그래서 주로 파일을 다룰때 사용된다.




UDP방식


UDP방식은 TCP와 달리 데이터의 전송의 흐름제어가 안된다.

다시말해 프로세스 A가 B에게 데이터를 전송하는 것이 '일방적'이며

데이터가 혹 누락된다면 데이터를 다시 보낼일은 발생하지 않는다는 말이다.

그래서 전송속도가 빠른 대신 정확하지 않다

사운드 등을 주고받을때 사용되는 방식이다.





socket()


헤더파일

#include <sys/types.h> 

#include <sys/socket.h>


원형

int socket(int domain, int type, int protocol);






      1 #include <sys/types.h>
      2 #include <sys/socket.h>
      3
      4 int main(void)
      5 {
      6     int iSock;
      7
      8     // IPv4를 사용하고 TCP방식 사용하는 소켓을 생성
      9     iSock = socket(AF_INET,SOCK_STREAM,0);
     10
     11     if(0 < iSock) // 에러발생시
     12     {
     13         printf("\n소켓을 생성 할 수 없음\n\n");
     14         return -1;
     15     }
     16
     17     printf("\niSock = [%d] 입니다.\n\n",iSock);
     18
     19     close(iSock);   // 소켓을 닫을때 close()사용
     20
     21     return 0;








- linux

--- socket.h 위치

/usr/inlcude/linux/socket.h

/usr/include/asm/socket.h















'부산IT학원 > 스마트컨트롤러' 카테고리의 다른 글

20140613 (TCP/IP)  (0) 2014.06.13
20140612 (초음파 모듈 Ultra Sonic)  (0) 2014.06.12
20140610 (TWI로 외부 EEPROM과 통신)  (0) 2014.06.10
20140609 (TWI)  (0) 2014.06.10
20140605 (TWI)  (1) 2014.06.05

설정

트랙백

댓글

20140610 (TWI로 외부 EEPROM과 통신)

77일차








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

ATMega2560 TWI로 외부 EEPROM과 통신

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



--- TWI도 속도에 관한 분주비가 있다.

- 계산식









- ATMega2560, EEPROM의 TWI 통신 순서








- write

소스)










- Read

소스)







지금까지 작성한 소스


main.zip


'부산IT학원 > 스마트컨트롤러' 카테고리의 다른 글

20140612 (초음파 모듈 Ultra Sonic)  (0) 2014.06.12
20140611 (Network)  (0) 2014.06.11
20140609 (TWI)  (0) 2014.06.10
20140605 (TWI)  (1) 2014.06.05
20140603 (추가보드에 7segment)  (0) 2014.06.03

설정

트랙백

댓글

20140609 (TWI)

76일차







-----------------------
ATMega2560 TWI
-----------------------







TWI 는 AVR 과 주변 디바이스 또는 AVR 사이에서 2 선만 사용해서 시리얼로 데이터를 전달하는 방식입니다.
I2C (TWI) 는 필립스에서 제안한 것으로, I2C (또는 IIC-Inter Integrated Circuit) 과 TWI 는 같은 것입니다.

그 특징은
1. 2 개의 선 (SDA, SCL) 으로 양방향 시리얼 통신을 합니다.

2  마스터와 슬레이브 동작을 할 수 있습니다.

3. 송신기와 수신기로 동작할 수 있습니다.

4. 7 비트 주소로 최대 128 개의 다른 슬레이브 주소를 만들수 있습니다.

5. 멀티 마스터 조정(Arbitration) 기능이 있습니다.

6. 최대 400 KHz 데이터 전달 속도를 지원합니다.

SCL 은 단방향으로 항상 마스터가 발생합니다.

SDA 는 양방향으로 데이터를 전달합니다.


마스터가 데이터를 송신할 때는 마스터 송신기(MT) 로 동작하고

마스터가 데이터를 수신할 때는 마스터 수신기(MR) 로 도작합니다.

슬레이브가 데이터를 송신할 때는 슬레이브 송신기(ST) 로 동작하고

슬레이브가 데이터를 수신할 때는 슬레이브 수신기(SR) 로 동작합니다.


TWI 버스 상에서 데이터(비트) 를 전달할 때는 클럭(SCL) 펄스에 의해서 수행된다

클럭이 높은 레벨일 때는 데이터는 일정하게 유지하고 있어야 합니다. 데이터의 변화는 클럭이 낮은

레벨일 때만 이루어 진다.

예외적으로 START 와 STOP 상태는 클럭이 높은 레벨에서 이루어 진다.




TWBR 은 비트 레이트 발생기의 나눗셈 비율을 결정합니다. 비트 레이트 발생기는 마스터 모드에서 SCL 의 주파수를 발생하는 주파수 분배기입니다.

TWCR 은 TWI 의 동작을 제어합니다.

7 TWINT (TWI Interrupt Flag)

TWI 의 현재 작업이 끝나고, 응용 프로그램의 응답을 기다릴 때 셋됩니다.  이 비트가 셋 된 동안은 SCL 은 로(LOW) 상태로 유지됩니다. 이 비트에 1 을 써서 소프트웨어적으로 클리어 시켜주어야 합니다. 인터럽트 루틴이 실행되어도 클리어 되지 않습니다.


6 TWEA (TWI Enable Acknoledge bit)

TWEA 비트는 확인 신호(Acknoledge) 펄스를 발생시키는 것을 제어합니다.

TWEA 비트에 1 을 쓰면 아래 조건을 만났을 때 ACK 신호를 발생합니다.

1. 디바이스의 슬레이브 주소를 수신했을 때

2. TWGCE 비트가 1 이고 general call 을 수신했을 때

3. 마스터 수신 또는 슬레이브 수신 상태에서 데이터 바이트를 수신했을 때


5 TWSTA (TWI START Condition bit)

2 선 시리얼 버스(Two-Wire Serial Bus) 상에서 마스터가 되고자 할 때 TWSTA 비트에 1 을 씁니다. TWI 하드웨어는 버스가 활용할 수 있는 상태인지를 체크하고나서 START 신호를 발생합니다.

TWSTA 비트는 START 비트가 발생한 후에 소프트웨어적으로 클리어시켜 주어야 합니다.


4 TWSTO (TWI STOP Condition bit)

마스터 모드에서 2 선 시리얼 버스(Two-Wire Serial Bus) 상에서 STOP 신호를 발생시키기 위하여 1 을 씁니다. TWSTO 비트는 자동으로 클리어됩니다.


3 TWWC (TWI Write Collistion Flag)

TWINT 비트가 로(LOW) 인 상태에서 TWI 데이터 레지스터(TWDR) 에 쓰려고 할 때 TWWC 플래그가 1 이 됩니다. 이 플래그는 TWINT 플래그가 1 인 상태에서 TWDR 레지스터에 쓰면 클리어 됩니다.


2 TWEN (TWI Enable bit)

TWEN 비트에 1 을 쓰면 TWI 작동이 가능하게 됩니다.

TWEN 비트에 0 을 쓰면 TWI 작동은 불가능으로 됩니다.


1 Res (Reserved Bit)


0 TWIE (TWI Interrupt Enable bit)

이 비트가 1 이고 SREG 레지스터의 I 비트가 1 로 셋되어 있으면 TWINT 플래그가 1 이 동안 TWI 인터럽트 요구가 활성화됩니다.

'부산IT학원 > 스마트컨트롤러' 카테고리의 다른 글

20140611 (Network)  (0) 2014.06.11
20140610 (TWI로 외부 EEPROM과 통신)  (0) 2014.06.10
20140605 (TWI)  (1) 2014.06.05
20140603 (추가보드에 7segment)  (0) 2014.06.03
20140602 (추가보드)  (0) 2014.06.03

설정

트랙백

댓글