'CSE(컴퓨터 공학)/유닉스 시스템'에 해당되는 글 3건

  1. 2009.05.30 유닉스 과제 #4
  2. 2009.05.06 유닉스 과제 #3
  3. 2009.04.08 유닉스 과제2
hared memory와 semapore를 사용하여 데이터를 공동관리 하는 두개이상의 프로세스로 구성된 프로그램을 작성하시오
(예 : 계산담당 프로세스와 디스플레이 담당 프로세스)


이과제를 해결하기 위해서 shared memory와 semapore에 대해서 확실히 알고 넘어가야한다.

먼제 세마포어(semapores)에 대해서 알고 넘어가도록 하자.


Semaphores
세마포어
A counter used to provide access to a shared data object for multiple process.
세마포어란 여러 프로세스가 공유된 데이터를 Access 할 수 있게 하는 Counter이다.

To obtain a shared resource a process
프로세스가 공유된 자원을 얻기 위해서는
1.Test the semaphore that controls the resource
세마포어를 테스트 해봐야하는데 그것은 리소스를 컨트롤 한다.
2. If the value of the semaphore is positive, the process can use the resource
세마포어의 값이 양수(positive)이면 프로세스는 자원을 이용할 수 있다.
-The process decrement the semaphore value by 1, indicating that it has used one unit of the resource.
프로세스는 세마포어의 값을 1을 감소시킨다, 그것은 리소스의 하나의 unit을 이용했다는것을 나타낸다.
3.If the value of the semaphore is 0, the process goes to sleep until the semaphore value is greater than 0
만약 세마포어 값이 0이라면 프로세스는 세마포어의 값이 0보다 커질때까지 슬립을 한다.
-When the process wakes up, it returns to sleep
프로세스가 깨어나면 그것은 슬립을 리턴시킨다.

To implement semaphores correctly,
세마포어를 올바르게 구현하려면
The test of a semaphore’s value and the decrementing of this value must be an atomic operation.
세마포어의 값의 테스트와 이값의 감소가 atomic operation이어야 한다.
For this reason, semaphores are normally implemented inside the kernel.
이런 연유로 세마포어는 커널 안에서 일반적으로 구현이 된다.


이론적 형태의 세마포어

-네덜란드의 이론가 E.W.Dijkstra가 프로세스의 동기화 해결책으로 제안
-세마포어 (sem)은 다음과 같이 연산이 허용되는 정수형 변수

p(sem) or wait(sem)
if (sem != 0)
    decrement sem by one
else
    wait until sem becomes non-zero

v(sem) or signal(sem)

if (queue of waiting processes not empty)
restart first process in wait queue
else
increment sem by one

-두 연산은 모두 atomic operations
`sem을 변경할 수 있는 프로세스는 한 순간에 오직 하나뿐이다.

세마포어의 좋은점: 세마포어의 불변특성 (unvariant)
-(semaphore’s initial value + number of v operations – number of completed p operations) >= 0
세마포어는 다방면으로 사용될 수 있다.
-가장 단순한 경우는 프로그램의 특정한 부분을 수행하는 프로세스가 한 순간에 오직 하나만 존재하도록 하는 mutual exclusion (상호배제)를 보장.

-예제:
(number of completed p operations – number of v operations) <= initial value of semaphore sem의 초기값이 1이면
(number of completed p operations – number of v operations) <= 1
즉 P와 V사이에 있는 문장들은 한 순간에 오직 하나의 프로세스에 의해서만 수행된다.

이런 이론으로 Unix에는 세마포어가 구현이 되어 있다. 유닉스에 세마포어 시스템 호출은 어떻게 이루어지는지 한번 살표보도록 하자. 커널에서는 세마포어를 위한 스트럭처를 이용하여 세마포어를 관리하게 된다. 이때 사용하는 스트럭처가 다음의 smid_ds이다.

struct semid_ds{
  struct ipc_perm sem_perm; //세마포어에 대한 접근권한
  struct sem *sem_base; /* ptr to first semaphore */
  ushort sem_nsems;     /* # of semaphores in set */
  time_t sem_otime;     /* last-semop() time *///마지막으로 세마포어와 관련된 작업을 수행하는 시간
  time_t sem_ctime;     /* last-change time *///마지막으로 스트럭처의 데이터들이 업데이트 된 시간
};

이제 Shared Memory에 대해서 알아보자.

Shared memory란 말그대로 프로세스들이 특정 메모리 영역을 공유하도록 만든 뒤, 이 공간을 이용하여 통신을 수행하는 기법이다. 메모리를 서로 공유하는 프로세스들은 공유 가상 메모리를 가리키는 테이블 엔트리를 가지게 된다.

다른 IPC 기법들과 마찬가지로 공유메모리는 킷값을 이용하여 잡근 및 관리가 된다. 공유 메모리 또한 프로세스 동기화가 필요하기 떄문에 세마포어 등을 이용하여 자원에 대한 관리를 해주어야 한다. 

유닉스 시스템은 shm_segs라는 벡터를 이용하여 공유 메모리를 관리하게 된다. 그리고 벡터속에는 shmid_ds라는 스트럭처가 저장이 되는데 shmid_ds를 이용하여 공유메모리 정보를 저장하게 된다. 

이것을 토대로 프로그램을 작성해보자.  프로그램은 두개의 프로그램으로 나뉘고 하나의 프로세스는 메시지를 받으려고 기다리는 프로세스이고 다른 하나는 메세지를 입력하는 프로세스가 될것이다. 이것을 shared memory와 세마포어를 이용해서 작성 하였다.

메시지를 보내는 프로그램은 우선 프로그램이 실행이 되면 세마포어의 값을 1감소시키고 메시지 입력을 대기한다. 메시지를 입력하면 세마포어의 값을 하나더 증가 시킨다.

메시지를 받는 프로그램은 세마포어를 감소하고 메시지를 출력한뒤 세마포어를 증가시키는데 세마포어가 보내는 프로그램쪽에서 차지하고 있다면 계속 대기상태가 되게 될것이다. 프로그램의 소스코드는 다음과 같다.


메시지를 보내는 프로그램
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
 
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
//공유 메모리 사이즈 설정
#define SIZE 64
 
//이프로세스는 상대편 프로세스가 메시지를 입력할때까지는 process가 돌면 안된다. 
//세마포어를 이용해서 막아보자
 
int main(int argc, char *argv[])
{
    //세마포어와 shared memory 변수선언
    void *s_memory = (void *)0;
    char *buffer;
    int semId, proId, smId;
    int isRun = 1;
 
    struct sembuf semB;
     
    //shmget을 이용하여 공유메모리를 확보한다.
    smId = shmget((key_t)9000, SIZE, 0666|IPC_CREAT);
    if(smId == -1)
    {
        printf("shmget 실행실패\n");
        return 0;
    }
 
    //shmat을 이용하여 공유 메모리 주소 얻기
    s_memory = shmat(smId, (void *)0, 0);
    if(s_memory == (void *)-1)
    {
        printf("shmat 실행실패\n");
        return 0;
    }
 
    //공유메모리 주소와 내부 변수 포인터연결
 
    buffer = (char *)s_memory;
 
 
    //sembuf의 초기값 설정
 
    semB.sem_flg = SEM_UNDO;
    semB.sem_num = 0;
 
    //semget을 이용해서 세마포어 ID를 구한다.
    semId = semget((key_t)1234, 1, 0666 | IPC_CREAT);
 
    //세마포어 초기값 설정
    if(semctl(semId, 0 , SETVAL, 1) == -1)
    {
        fprintf(stderr, "세마포어 초기화 실폐\n");
        exit(0);
    }
    //본프로세스의 PID출력
    printf("본프로세스의PID값은 : %d\n", getpid());
     
     
 
 
    //프로세스의 입무를 수행하기 전에 세마포어값을 감소 후 수행 그다음 세마포어 값을 다시 증가 시킨다.
    while(isRun)
    {
        //세마포어에 마지막으로 수정을 가한 프로세스 PID 출력
        proId = semctl(semId, 0, GETPID, 0);
        printf("세마포어를 변경한 마지막 PID: %d\n", proId);
 
             
        //세마포어의 값을 감소시킨다.
        semB.sem_op = -1;
        if(semop(semId, &semB, 1) == -1)
        {
            fprintf(stderr, "세마포어 값감소 실패\n");
            exit(0);
        }
         
        printf("메시지입력 : "); 
        scanf("%s",buffer);
               
        //quit을 보내면 종료
        if(!strcmp(buffer, "quit"))
        {
            break;
        }

 

        //세마포어 값을 증가시킨다.
        semB.sem_op = 1;
        if(semop(semId, &semB, 1) == -1)
        {
            fprintf(stderr, "세마포어 값증가 실패\n");
            exit(0);
        }
    }
 
    //프로세스와 공유메모리 분리
 
    if(shmdt(s_memory) == -1)
    {
        printf("shmdt 실행실패\n");
        return 0;
    }
 
    return 1;
}


메시지를 받는 프로그램

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
 
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
//공유 메모리 사이즈 설정
#define SIZE 64
 
//이프로세스는 상대편 프로세스가 메시지를 입력할때까지는 process가 돌면 안된다. 
//세마포어를 이용해서 막아보자
 
int main(int argc, char *argv[])
{
    //세마포어와 shared memory 변수선언
    void *s_memory = (void *)0;
    char *buffer;
    int semId, proId, smId;
    int isRun = 1;
 
    struct sembuf semB;
     
    //shmget을 이용하여 공유메모리를 확보한다.
    smId = shmget((key_t)9000, SIZE, 0666|IPC_CREAT);
    if(smId == -1)
    {
        printf("shmget 실행실패\n");
        return 0;
    }
 
    //shmat을 이용하여 공유 메모리 주소 얻기
    s_memory = shmat(smId, (void *)0, 0);
    if(s_memory == (void *)-1)
    {
        printf("shmat 실행실패\n");
        return 0;
    }
 
    //공유메모리 주소와 내부 변수 포인터연결
 
    buffer = (char *)s_memory;
 
 
    //sembuf의 초기값 설정
 
    semB.sem_flg = SEM_UNDO;
    semB.sem_num = 0;
 
    //semget을 이용해서 세마포어 ID를 구한다.
    semId = semget((key_t)1234, 1, 0666 | IPC_CREAT);
 
    //세마포어 초기값 설정
    if(semctl(semId, 0 , SETVAL, 1) == -1)
    {
        fprintf(stderr, "세마포어 초기화 실폐\n");
        exit(0);
    }
    //본프로세스의 PID출력
    printf("본프로세스의PID값은 : %d\n", getpid());
     
     
 
 
    //프로세스의 입무를 수행하기 전에 세마포어값을 감소 후 수행 그다음 세마포어 값을 다시 증가 시킨다.
    while(isRun)
    {
        //세마포어에 마지막으로 수정을 가한 프로세스 PID 출력
        /*proId = semctl(semId, 0, GETPID, 0);
        printf("세마포어를 변경한 마지막 PID: %d\n", proId);*/
 
            
        if(!strcmp(buffer,""))continue; //일단 버퍼가 없으면 그냥 계속 지나쳐간다.
        
        //세마포어의 값을 감소시킨다.
        semB.sem_op = -1;
        if(semop(semId, &semB, 1) == -1)
        {
            fprintf(stderr, "세마포어 값감소 실패\n");
            exit(0);
        }
 
        
        printf("받은 메시지 : %s\n", buffer); //메시지를 출력해준다.
        
        //quit을 받으면 종료
        if(!strcmp(buffer, "quit"))
        {
            break;
        }
        
 
        //세마포어 값을 증가시킨다.
        semB.sem_op = 1;
        if(semop(semId, &semB, 1) == -1)
        {
            fprintf(stderr, "세마포어 값증가 실패\n");
            exit(0);
        }
        
 
    }
 
    //프로세스와 공유메모리 분리
 
    if(shmdt(s_memory) == -1)
    {
        printf("shmdt 실행실패\n");
        return 0;
    }
 
    //공유메모리 제거
    if(shmctl(smId, IPC_RMID, 0) == -1)
    {
        printf("shmctl 실행실패\n");
        return 0;
    }
     
 
    //세마포어를 제거한다.
    if(semctl(semId, 0, IPC_RMID, 0) == -1)
    {
        fprintf(stderr, "세마포어 제거 실패\n");
        exit(0);
    }
 
 
    return 1;
}


실행 결과는 다음과 같다.



다음과 같이 메시지를 주고 받을 수 있게 프로그램이 수행된다.







'CSE(컴퓨터 공학) > 유닉스 시스템' 카테고리의 다른 글

유닉스 과제 #3  (0) 2009.05.06
유닉스 과제2  (0) 2009.04.08
Posted by 태씽

(1) 주어진 파일을 Copy 하는 프로그램을 작성하되, copy SIGINT SIGQUIT을 받아도 죽지않고 COPY를 모두 마치닣후 종료하는 프로그램을 작성하라.

주어진 파일을 카피하는 과정이야 어떤 파일명을 받아서 그파일을 바이너리로 읽어 그대로 다른 파일명으로 써주면 된다. 쉽게 할 수 있는 과정인데 이꽈제는 SIGINT SIGQUIT의 기능을 먼저 이해를 해야 한다.

유닉스에서는 signal.h 헤더파일에 정의된 시그널 이름들이 있는데 그중에 SIGINT, SIGQUIT이 있다


#define SIGINT :
인터럽트를 위한 시그널로 유저가 인터럽트를 발생시키는 키를 입력했을 때 그와연결된 프로세스에세 커널이 보내는 시그널이다. 이 시그널은 프로세스를 종요할 때 많이 사용되는 시그널이다.이것은 터미널에서 ctrl+c와 같은 역활을 한다.

#define SIGQUIT : Quit
를 위한 시그널로 유저가 터미널에서 Quit키를 치면 커널이 프로세스에게 SIGQUIT 시그널을 보낸다


그러면 C언어상에서 이들 시그널을 어떻게 이용을하는가 하는것에 대한 궁금증이 생길텐데 한번 예제코드를 이용해서 살펴보도록하자.

signal()
이라는 함수가 있는데 이 함수는 이름그대로 시그널을 처리하는 함수 이다. signal 함수는 다음과 같이 나타낼 수 있다.

 int sigkind;
int function();
signal(sigKind, function);


여기서 sigKind에 바로 SIGINT, SIGQUIT같은 것이 들어가는것이다.
그리고 function sigKind의 함수가 호출 되었을때 실행되는 함수 이다
.

이제 이 signal함수를 이용해서 간단한 코드를 작성해 보자

 

 #include<stdio.h>
#include<signal.h>

int handler()
{

//필요한 작업을 처리한후에 프로그램을 종요한다.
printf("\n\nSIGINT
핸들러 호출
\n");
printf("\n<<
작업 종료 시작
>>>\n");
sleep(1);
printf("\n\n
실행되는 모든 프로세스 종료
\n\n");
printf("All open file closed\n\n\n");
exit(1);

}

int main()
{

int result, step =0;
//signal
함수를 사용하여 SIGINT 핸들러와 SIGQUIT핸들러를 등록한다.
printf("signal
함수를 사용하여 SIGINT 핸들러를 SIGQUIT핸들러를 등록한다
.\n\n");
result = signal(SIGINT, handler);
result = signal(SIGQUIT, handler);

//
무한루프 프로그램을 돌린다
.
printf("
메인 프로세스 실행
.\n");
while(1)
{

step++;
printf("%d
번째 작업 수행\n", step);
sleep(1);

}
return 1;

}




위의 코드를 실행하면 무한 루프대로 작업수행 메시지가 뜰것이고 시그널이 들어오면 핸들러 함수가 동작 하고 프로그램이 종료가 될 것이다. 실행을 시켜보자. 프로그램 실행도중 ctrl + c 또는 ctrl + \ 키를 눌러보자.

 ktss1023@ktss1023-desktop:~/바탕화면$ ./a.out
signal
함수를 사용하여 SIGINT 핸들러를 SIGQUIT핸들러를 등록한다
.
 
메인 프로세스 실행
.
1
번째 작업 수행

2
번째 작업 수행
3
번째 작업 수행


SIGINT
핸들러 호출

<<
작업 종료 시작>>>


실행되는 모든 프로세스 종료


All open file closed



다음과 같이 잘 실행이 될 것이다.

그런데 이것으로는 과제의 내용에 합당하는 문제를 풀수가 없다. 그러면 어떤 시그널이 들어올때 그시그널을 미리 block을 시킬수 있는 함수가 있다면 좋을 것이다. 바로 그것이 문제를 해결 하기위한 함수이다. 어떤함수가 있는지 한번 살펴 보자.

int sigemptyset(sigset_t * set)

set 이 가리키고 있는 시그널 집합을 초기화 한다.

성공시에 0 return 하고, 실패시에 -1 return 한다.

int sigaddset(sigset_t * set, int signum)

set 이 가리키고 있는 시그널 집합에 signum을 추가한다.

성공시에 0 return 하고, 실패시에 -1 return 한다.

int sigdelset(sigset_t * set, int signum)

set 이 가리키고 있는 시그널 집합에 signum을 삭제한다.

성공시에 0 return 하고, 실패시에 -1 return 한다.

int sigprocmask(int how, const sigset_t * set, sigset_t * oldset)

시그널 마스크를 검사하고 변경하기 위해서 사용된다. 간단히 말해서 해당 시그널에 대해서

BLOCK, UNBLOCK 를 하기 위해서 사용한다.

 

how option

SIG_BLOCK

새로운 시그널 마스크는 현재의 시그널 마스크와 set에 의해 지정된 시그널 마스크의 합집합이다.

, set는 블록 시키고자 하는 추가적인 시그널들을 포함한다.

SIG_UNBLOCK

새로운 시그널 마스크는 현재의 시그널 마스크와 set로 지정된 시그널 마스크의 보수의 교집합이다.

, set는 블럭에서 해제시킬 시그널들을 포함한다.

SIG_SETMASK

새로운 시그널 마스크는 set로 지정된 시그널 마스크이다.

시그널 마스크를 변경하였다가 이전 시그널 마스크로 복귀시키고자 할 때, 원래의 시그널 마스크를

저장하였다가 SIG_SETMASK 옵션을 사용해야 한다

sigprocmask 는 성공시에 0 return 하고, 실패시에 -1 return 한다.


바로 이런 함수이다. sigset_t * set 집합을 하나 선언하고 집합에 SIGINT, SIGQUIT을 추가 한뒤int sigprocmask(int how, const sigset_t * set, sigset_t * oldset) 함수를 이용해서 SIG_BLOCK을 해주면 된다.

문제를 풀기 전에 이 함수를 이용해서 아까의 프로그램을 약간 변형하는 형태의 프로그램을 작성해보자.

 

 #include<stdio.h>
#include<signal.h>

int main(void)
{
int result, step =0;
sigset_t* set, oldset, pendset;//BLOCK
signal set의 변수를 선언,

//
셋을 초기화 시킨다
.
if(sigemptyset(&set)<0)
{
printf("sigemptyset error\n");
exit(1);
}

//
블록할 시그널 등록

if(sigaddset(&set, SIGINT)<0)
{
printf("sigaddset error\n");
exit(1);
}

//
블록할 시그널 등록
if(sigaddset(&set, SIGQUIT)<0)
{
printf("sigaddset error\n");
exit(1);

}

//
시그널 블록을 해준다.
printf("
시그널 블록 시작
\n\n");
printf("5
초만기다려봐
\n");
if(sigprocmask(SIG_BLOCK, &set, &oldset)<0)
{
printf("
블록 에러
");
exit(1);
}



sleep(5);

if(sigpending(&pendset)<0)
{
printf("sigpending error");
exit(1);
}

if(sigismember(&pendset, SIGINT)||sigismember(&pendset, SIGQUIT))
{
printf("
작업이 진행중이다
.\n");
}


//
무한루프 프로그램을 돌린다
.
printf("
메인 프로세스 실행
.\n");
while(1)
{
step++;
printf("%d
번째 작업 수행
\n", step);
sleep(1);
if(step==20)break;

}


if(sigprocmask(SIG_SETMASK, &oldset, NULL) <0)
{
printf("unblock error");
exit(1);
}
printf("SIGQUIT,SIGINT
가 언블록되었다
\n\n");


return;
}



위의 프로그램은 처음 5초간 기다리면서 신호를 받아 그신호가 SIGINT, SIGQUIT이면 메시지를 출력할려고 그랬는데 이걸지속적으로 할려면 타이머나 스레드를 돌려서 해야 될 듯 하다. 그리고 sigprocmask 함수를 이용해서 SIGINT,SIGQUIT의 신호를 막았다. 그러므로 실행하면 작업이 끝나기전에는 신호가 블록이 되어 동작하지 않는다.
자 그렇다면 이제 위를 토대로 문제를 해결하면 된다. 작업에 파일을 복사하는 작업을 하면 되는것이다. 처음에 폴더내에있는 파일명들을 출력해주고 파일명을 입력 받고 복사해주면 되는 형식으로 작성을 해보자.

#include<stdio.h>
#include<signal.h>
#include<fcntl.h>

#define LENGTH 256

static void sig_int(int);

int main()
{
char filename[LENGTH];//
파일이름은 100자를 초과할 수 없다.

char c_filename[LENGTH];//
복사할 파일 이름


char buf[LENGTH];//
버퍼


int result= 0;
int readCnt, writeCnt, orgFile, newFile;

sigset_t* set,oldset;//BLOCK
signal set의 변수를 선언,    

//
복사할 파일 오픈

printf("
복사할 파일 이름을 입력하시오 : ");
scanf("%s",filename);
orgFile = open(filename, O_RDONLY);

if(orgFile<0)
{
    printf("
없는 파일입니다
.\n");
    return -1;
}

//
복사 당할 파일 생성

printf("
생성할 파일 이름을 입력하시오 : ");
scanf("%s",c_filename);
newFile = open(c_filename, O_WRONLY|O_CREAT|O_APPEND);

if (signal(SIGINT, sig_int) == SIG_ERR)
{
printf("signal error\n");
return -1;
}
if (signal(SIGQUIT, sig_int) == SIG_ERR)
{
printf("signal error\n");
return -1;
}

//
셋을 초기화 시킨다
.
if(sigemptyset(&set)<0)
{
printf("sigemptyset error\n");
return -1;
}

//
블록할 시그널 등록

if(sigaddset(&set, SIGINT)<0)
{
printf("sigaddset error\n");
return -1;
}

//
블록할 시그널 등록
if(sigaddset(&set, SIGQUIT)<0)
{
printf("sigaddset error\n");
return -1;

}

//
시그널 블록을 해준다.



/*
sleep(5);

if(sigpending(&pendset)<0)
{
printf("sigpending error");
exit(1);
}

if(sigismember(&pendset, SIGINT)||sigismember(&pendset, SIGQUIT))
{
printf("
작업이 진행중이다
.\n");
}
*/
printf("
시그널 블록 시작
\n\n");
if(sigprocmask(SIG_BLOCK, &set, &oldset)<0)
{
printf("
블록 에러
");
return -1;
}


//
프로그램을 돌린다
.
printf("
복사 시작
!\n");

for(readCnt=1;readCnt>0;)
{

    readCnt = read(orgFile, buf, LENGTH);
    writeCnt = write(newFile, buf, LENGTH);
    
    /*
    step++;
    if(step%2==0)
    {
        printf("1
초만쉬어요
.\n");
        sleep(1);
    }
*/
    printf("%d, %d\n", readCnt, writeCnt);


}

printf("SIGQUIT,SIGINT
가 언블록되었다
\n\n");
}

printf("
복사끝
\n");

close(orgFile);
close(newFile);

if(sigprocmask(SIG_SETMASK, &oldset, NULL) <0)
{
printf("unblock error");
return -1;
}

static void sig_int(int signo)
{
    printf("
신호확인
\n");
    return;
}

하지만 이코드로는 왠일인지 파일 복사가 진행이 되지 않는다. 대체 무슨 연유일까? 이것만으로는 해결하기가 어렵다는 결론으로 sigaction이라는 함수를 찾아 보았다.

sigaction()

sigaction은 시그널을 취급할 방법을 선택 할 수 있다.

헤더파일

#include <signal.h>

원형

int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);

인자

첫번째 : 행동을 지정할 개개의 시그널

두번째 : 지정하고 싶은 행동

세번째 : 나중에 복구를 위해 현재 값을 저장한다.

다음과 같이 시그널의을 취급하는 방법을 선택하는 함수이라고 하는데.
일단, sigaction 구조체에 대해서도 알아봐야 할 것이다.

struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
}
sa_handler

signum번호를 가지는 시그널이 발생했을 때 실행된 함수를 설치한다. 함수외에도 SIG_DFL SIG_IGN을 지정할 수 있다. 전자는 시그널에 대한 기본행동을 후자는 시그널을 무시하기 위해서 사용한다.

sa_mask 

sa_handler에 등록된 시그널 핸들러 함수가 실행되는 동안 블럭되어야 하는 시그널의 마스크를 제공한다. SA_NOMASK가 적용되어 있지 않다면

sa_flags 

시그널 처리 프로세스의 행위를 수정하는 일련의 플래그들을 명시한다. 다음중 하나 이상의 것들에 의해서 만들어 진다.

SA_NOCLDSTOP 

만약 signum SIGCHLD라면, 자식 프로세스가 SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU등을 받아서 중단되었을 때 이를 통지 받을 수 없게 된다.

SA_ONESHOT, SA_RESETHAND

일단 시그널 처리기가 호출되면, 기본 상태에 대한 시그널 액션을 재 저장한다. 이는 signal(2)호출에 대한 기본 행위이다.

SA_RESTART 

일부 시스템 호출들이 시그널을 통해 재시작할 수 있도록 함으로서 BSD 시그널과 호환되도록 한다.

SA_NOMASK, SA_NODEFER

시그널이 자체 시그널 처리기로부터 수신 받지 않도록 한다.

SA_SIGINFO 

시그널 처리기가 하나가 아닌 3개의 인자를 취할경우 sa_handler대신 sigaction siginfo_t를 이용할 수 있다. siginto_t는 다음과 같이 정의된 구조체이다.

[

 

다음과 같은 구조체를 지니는데 각구조체의 역확은 예제 코드에서 알아보자.

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

---------------------test.c----------------------------

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

//sigaction() 사용하기
//
시그널을 취급할 방법을 선택할 수 있다.
#include <stdio.h>
#include <signal.h>
//sigaction()

int main(void)
{
 static struct sigaction act;
//행동을 지정할 구조체

 void catchint(int); //행동할 함수(함수를 바꿈으로 인터럽트시 행동을 조절 할 수 있다.)

 act.sa_handler = catchint; //구조체 변수에 취해질 행동을 지정한다.
 
 sigfillset(&(act.sa_mask));
//시그널을 포함하도록 집합을 완전 채운다.(?)
 
 //SIGINT : 인터럽트를 프로세스에게 보낸다.
 //&act
지정된 행동
 //
현재값은 0으로(나중에 복구하기 위해 지정할 수 있다.)
 sigaction(SIGINT, &act, NULL);
 
 //프로세스 실행
 printf("sleep call #1\n"); sleep(5);
 printf("sleep call #2\n"); sleep(5);
 printf("sleep call #3\n"); sleep(5);
 printf("sleep call #4\n"); sleep(5);

 printf("Exlting\n");
 exit(0);
}

//실행 함수
//
인터럽트를 칠때 커널에 의해 프로세스에게 보내진다.
void catchint(int signo)
{

 printf("\nCATCHINT : signo = %d\n", signo);
 printf("CATCHINT : returning\n\n");
}

==================================================================


다른 블로그의 글을 퍼온 코드인데 이해 하기가 쉬울것이다. 그러니까 sigaction signal 함수를 좀더 발전? 시킨것이다. 이를 토대로 코드를 한번 작성해보자.

#include<stdio.h>
#include<signal.h>
#include<fcntl.h>
#include<stdlib.h>

#define LENGTH 256

void sigcatch(int); //
행동할 함수(함수를 바꿈으로 인터럽트시 행동을 조절 할 수 있다.)

int main(void)
{
    char filename[LENGTH];//
파일이름은 100자를 초과할 수 없다
.

    char c_filename[LENGTH];//
복사할 파일 이름


    char buf[LENGTH];//
버퍼

    int result= 0;
    int readCnt, writeCnt, orgFile, newFile;

    static struct sigaction act; //
행동을 지정할 구조체
    
    act.sa_handler = sigcatch; //
구조체 변수에 취해질 해동을 지정한다.
 
    sigfillset(&(act.sa_mask)); //sigaction
구조체의 sigset을 모든 신호를 다 채운다
.
    //
이는 sigaddset함수를 이용해 SIGINT SIGQUIT만 선택해도 된다
.
    act.sa_flags = 0;
   
    //SIGINT :
인터럽트를 프로세스에게 보낸다
.
    //&act
지정된 행동

    //
현재값은 0으로(나중에 복구하기 위해 지정할 수 있다.)
    if(sigaction(SIGINT, &act, NULL)==-1)
    {
        perror("sigaction error\n");
    }
    if(sigaction(SIGQUIT, &act, NULL)==-1)
    {
        perror("sigaction error\n");
    }
    //


    //
복사할 파일 오픈

    printf("
복사할 파일 이름을 입력하시오 : ");
    scanf("%s",filename);
    orgFile = open(filename, O_RDONLY);
    
    if(orgFile<0)
    {
        printf("
없는 파일입니다
.\n");
        return -1;
    }
    
    //
복사 당할 파일 생성

    printf("
생성할 파일 이름을 입력하시오 : ");
    scanf("%s",c_filename);
    newFile = open(c_filename, O_WRONLY|O_CREAT|O_APPEND);


    //
프로그램을 돌린다
.
    printf("
복사 시작
!\n");

    for(readCnt=1;readCnt>0;)
    {
        readCnt = read(orgFile, buf, LENGTH);
        writeCnt = write(newFile, buf, LENGTH);
    
           /*
             step++;
            if(step%2==0)
            {
                printf("1
초만쉬어요
.\n");
                sleep(1);
            }
        */
        printf("%d, %d\n", readCnt, writeCnt);

    
    }

    printf("
복사끝
\n");

    close(orgFile);
    close(newFile);
}

//
실행 함수

//
인터럽트를 칠때 커널에 의해 프로세스에게 보내진다.
void sigcatch(int signo)
{

 printf("\nsigno = %d
신호가 들어왔습니다
.\n", signo);
 printf("
파일 복사가 진행중입니다.\n\n");

}

단순히 텍스트 파일 복사를 시험하였고 복사가 빨리 진행되므로 255바이트 마다 1초간 sleep함수를 실행했다.
결과는




(2) alram() SIGALRAM을 사용하여 timeout을 가지고 file read하는 함수를 작성하시오.

일단 alarm() SIGALRM에 대해서 알아 보아야 할것이다. 우선 alarm함수는 시그널을 전달하기 위해서 사용하는 함수로 전달되는 시그널이 SIGALRM이다.

alarm seconds 초 후에 프로세스에 SIGALRM 을 전달한다. 만약 seconds 0이라면 결코SIGALRM 이 전달되지 않을것이다. 만약 alarm 이 여러개 쓰인다면 기존에 설정되었던 alarm 설정값은 취소되고가정최근의 alarm 설정값으로 지정된다.

그러므로 alarm 을 사용할때는 alarm 이 겹치지 않도록 주의해야 한다.

SIGALRM 의 기본 행동은 프로세스 종료이다.

이렇게 나와 있는 예제프로그램을 한번 보자.

#include <unistd.h>
#include <signal.h>

void myalarm()
{
   
printf("ding dong dang\n");
}

int main()
{
   
printf("alarm setting\n");
    // SIGALRM
이 발생하면 myalarm() 함수를 실행한다.
   
signal(SIGALRM, myalarm);
    //
알람을 1초로 설정한다.
   
alarm(1);
  

 while(1)
    {
       
printf("ok\n");
        //
신호를 기다린다.
       
pause();
        // alarm
2초로 설정한다.
       
alarm(2);
    }
}

다음과 같이 signal 함수를 이용해서 SIGALRM이 발생할 경우 myalarm함수가 발생하도록 했다.
위코드를 토대로 타임아웃을 주는 파일을 리드하는 프로그램을 작성하자. 읽은 파일은 화면에 출력하는것으로 한다.

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

void myalarm()
{
    printf("\n
파일리드를 종료합니다.\n");
    exit(0);
}

int main()
{
    FILE *fp;//
리드할 파일포인터

    char f_name[200];//
파일이름
    char buf;//1
바이트짜리 버퍼
    int limit;//alarm
의 수를 결정

    printf("
읽을 파일이름을 입력하세요 : ");
    scanf("%s",f_name);
    
    if(!(fp = fopen(f_name,"r")))
    {    
    printf("
파일이 잘못되었습니다
.\n");
    return -1;
    }    

    printf("
몇초후 타임아웃하실건가요
: ");
    scanf("%d",&limit);

    signal(SIGALRM, myalarm);
    
    alarm(limit);
    printf("alarm setting\n");
    // SIGALRM
이 발생하면 myalarm() 함수를 실행한다
.
   

    while(!feof(fp))//
파일은 1바이트씩 읽고 출력한다
.
    {
        printf("%c",fgetc(fp));
        sleep(1);
    }
    return 0;    
}

위 프로그램은 미리 alram을 설정하고 그동안 1바이트씩을 읽고 출력한뒤 sleep(1)을해주는 프로그램으로 20초동안만 파일을읽고 출력이 된다.

 

 

 

'CSE(컴퓨터 공학) > 유닉스 시스템' 카테고리의 다른 글

유닉스 과제 #4  (0) 2009.05.30
유닉스 과제2  (0) 2009.04.08
Posted by 태씽
setjmp 와 longjmp를 이용한 간단한 프로그램을 작성하라.

#include<setjmp.h>

int setjmp(jmp_buf env);
void longjum(jmp_buf env, int val);

setjmp
- setjmp를 호춯한 함수의 스택 환경과 레지스터 환경을 env에 저장
- 직접 호출된 경우에 0을, longjmp에 의해 호출된 경우에는 0 이외의 지정된 값을 리턴한다.

longjmp
- 현재의 스택 프레임을 setjmp에 의해 저장된 스택 환경과 레지스터 환경으로 복원, setjmp가 지정한 위치로 제어가 이동
- val 값은 setjmp의 리턴값, 여러개의 longjmp 함수도 함꼐 사용 가능
- env 변수는 전역 변수로 선언 한다.

위 두함수는 비지역적 분기로서 지역 분기 goto문과는 달리 스택 정보를 복원할 수 있어야 한다. 위 두함수는 비지역 분기로 서 중첩된 함수 호출에서의 인터럽트나 에러를 처리하는데 효과적이다.

비지역적 분기와 변수값의 변화

변수값의 변화
- 메모리에 저장된 변수들은 longjmp 호출시의 값을 유지
- CPU나 레지스터에 저장된 변수들은 setjmp 설정시의 값으로 복원

최적화 컴파일
- 최적화하지 않는 경우에는 레지스터 변수 이외의 변수는 메모리에 할당한다.
- 최적화 하는 경우에는 휘발성 변수만 메모리에 할당하고 나머지는 레지스터에 할당한다. 단, 레지스터에 저장된 값을 보장하지는 않는다.


예제 프로그램

#include<stdio.h>
#include<setjmp.h>

static void f1(int, int, int);
static void f2(void);

static jmp_buf jmpbuffer; //jump 버퍼 설정

int main(void)
{
    int count; //count 변수는 일반 변수
    register int val; //val 변수는 레지스터
    volatile int sum; //sum 변수는 휘발성
   
    count=2; val=3; sum=4;

    if(setjmp(jmpbuffer) ! = 0)//setjmp결과가 0이 아니면 longjmp에 의해서 호출된 경우
    {
        //setjmp는 jmpbuffer에 현재 스택환경과 레지스터를 저장한다.(그러니까 복귀할때 여기로 복귀가 된다는 말!)
        //만약 longjmp가 호출 되어서 jmpbuffer에 저장되 어있는 스택환경과 레지스터 환경이 복원 되면
        //이 경우 아래 문장이 실행이 된다.
        printf("롱점프를 하면 : count = %d, val = %d, sum = %d", count, val, sum);
        exit(0);   
    }
   
    count = 97; val = 98; sum = 99;
   
    f1(count, val, sum);
}

static void f1(int i, int j , int k)
{
    printf("f1() : count = %d, val = %d, sum = %d", i, j, k);
    f2();
}

static void f2(void)
{
    longjmp(jmpbuffer, 1);
    //longjmp 의 두번째 파라미터는 setjmp의
}


자세한 설명은 주석으로 처리하였고 실행 결과 컴파일 옵션을 넣을때와 아닐때가 다른것을 알 수있다.
위 그림은 컴파일 옵션을 넣지 않았을 때인데 이경우 모두 뒤의 결과를 따르게 된다.


위 그림은 컴파일 옵션에 -O를 넣었을때는 휘발성 변수 뺴고는 다 레지스터 영역에 저장하여 longjmp시 setjmp가 호출 되었던 영역으로 돌아가게 될때 레지스터 영역에 있는 변수가 복귀가 된다. count와 val은 레지스터 영역에 저장 sum은 메모리 영역이므로 count와 val은 2, 3으로 복귀가 되지만 sum은 99 그래로이다.








'CSE(컴퓨터 공학) > 유닉스 시스템' 카테고리의 다른 글

유닉스 과제 #4  (0) 2009.05.30
유닉스 과제 #3  (0) 2009.05.06
Posted by 태씽