시그널에 대한 더 자세한 내용과 그 외의 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

sigaction을 왜 사용할까?

이전 UNIX의 signal함수보다 더 정교한 작업이 가능한 함수입니다. 그 정교한 작업이라는 게 어떤 것들이 있을까요? 

  • 정교한 작업1 (sigaction 코드 예제 - 자식 프로세스의 현재 상태 확인)

예를 들어서 자식이 종료하면 자식 프로세스에서 SIGCHLD 신호를 자식 프로세스에서 발생이 됩니다. 그러면 부모프로세스는 자식이 종료하였는지를 알 수 있죠. 그런데 많은 분들은 자식이 종료(EXIT)할때만 SIGCHLD를 발생시키는 것으로 알고 있습니다. 그런데요. 자식 프로세스는  자신이 정지(STOP)되었을 때, 혹은 재개(CONTINUED)되었을 때, 하물며 다른 프로세스에 의해 죽었을때 (KILL) 역시 부모 프로세스에 SIGCHLD를 보내게 됩니다. 우리가 signal 콜만 이용했을 경우 이러한 차이점을 부모 프로세스가 알아서 세세하게 제어할 수가 없습니다. 그런데 sigaction을 그런 차이들을 알아내어 컨트롤이 가능합니다. 이에 대한 예제 코드는 sigaction에 대해서 설명한 이후에 등장합니다.

  • 정교한 작업2 (sigaction 코드 예제  - 시그널 함수 구현)

뿐만 아니라 read 시스템 콜이 발생하여 사용자로부터 입력을 기다리고 있는 도중에, 시그널이 발생했다고 가정해보세요. 이럴 경우 시그널 핸들러 수행 이후에 1)read를 다시 호출해서 사용자 입력을 받을까요? 아니면 그냥 read는 넘어가고 다음 코드부터 수행할까요? 이러한 제어는 어떻게 해야하는 건가요? 이렇게 재개를 할지, 말지도 sigaction을 통해서 정할 수 있습니다. 물론 재개할지 말지 정하는 sigaction 사용 예제 코드는 밑에 있습니다.  

그전에 이 함수를 알기 위해서는 어느정도 시그널에 대한 기본지식이 있어야합니다. 시그널 집합, 시그널 차단 등의 개념이 나오기 때문인데요. 아래의 포스팅을 통해서 개념을 잡고 오시면 될것 같네요.

https://reakwon.tistory.com/46

 

[리눅스] 시그널 (SIGNAL)1 시그널 다루기(시그널 핸들러)

시그널 의미전달에 사용되는 대표적인 방법은 메세지와 신호입니다. 메세지는 여러가지 의미를 갖을 수 있지만 복잡한 대신 , 신호는 1:1로 의미가 대응되기 때문에 간단합니다. 컴퓨터에서 신

reakwon.tistory.com

https://reakwon.tistory.com/53

 

[리눅스] 시그널 (SIGNAL) 2 시그널 함수 sigprocmask, sigfillset, sigemptyset, sigaddset, sigdelset

시그널 관련 함수 지난 시간에는 간단하게 시그널 기본적인 설명과 어떻게 핸들링하는지에 대해서 알아보았죠? 시그널 개념과 시그널 핸들러는 지난 포스팅을 참고하시기 바랍니다. https://reakwo

reakwon.tistory.com

 

https://reakwon.tistory.com/54

 

[리눅스] 시그널(Signal) 3 sigpending, sigismember, sigsuspend

시그널에 대해서 이야기하는 3번째 시간이 되겠네요. 지난 번에는 시그널 개념과 시그널 관련함수까지 다루어 봤습니다. 시그널 개념과 시그널 핸들러와 시그널 관련 함수(sigfillset, sigemptyset, s

reakwon.tistory.com

 

sigaction

이 함수를 이용하면 어느 특정 신호에 관련된 동작을 조회할 수 있고 수정할 수 있습니다. sigaction의 원형은 이렇습니다.

#include <signal.h>

int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

 

사용하기 위해서는 signal.h 헤더 파일을 include시켜줘야합니다. 

▶ signum : signum은 시그널 번호를 의미합니다. signal함수의 처음 인자와 같습니다. 이런거 있잖아요. SIGINT, SIGQUIT 같은 시그널 번호말이죠. 단, 제어불가한 신호 번호는 SIGKILL과 SIGSTOP입니다. 

act : sigaction 구조체인 act 인자는 signum에 대해서 어떤 동작을 취할지에 대한 정보를 담고 있습니다. 즉, 시그널에 대한 동작을 수정하는 정보입니다.

oact : 역시 sigaction구조체인데, 이전에 설정된 동작에 대해서 돌려줍니다. 즉, 시그널에 대한 동작을 조회하는 정보입니다.

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입니다.
  •  sa_sigaction : sa_flags로 SA_SIGINFO를 사용할때 설정할 수 있습니다. 이런 경우에는 sa_handler가 사용되지 않고 이 sa_sigaction이 대신 사용됩니다. sa_sigaction에서는 신호 처리부(신호를 처리하는 함수)에 두가지 정보를 더 담아서 보냅니다. siginfo_t와 프로세스 문맥의 식별자가 그것입니다. 
    • 가장 처음 int는 시그널 번호입니다.
    • siginfo_t는 시그널에 대한 부가적인 정보를 담은 구조체입니다. 어떤 정보를 포함하는지는 이 구조체를 참고하면 됩니다. 시그널 정보에 많은 정보들이 들어가기 때문에 필드가 많으니 리눅스 메뉴얼을 참고하시기 바래요! 짧막하게 보면 아래와 같은 정보가 들어갈 수 있습니다.
    • 이후 void* 는 커널이 저장해둔 signal context의 정보를 담습니다.
siginfo_t {
   int      si_signo;     /* Signal number */
   int      si_errno;     /* An errno value */
   int      si_code;      /* Signal code */
   int      si_trapno;    /* Trap number that caused
                             hardware-generated signal
                             (unused on most architectures) */
   pid_t    si_pid;       /* Sending process ID */
   uid_t    si_uid;       /* Real user ID of sending process */
   int      si_status;    /* Exit value or signal */
   clock_t  si_utime;     /* User time consumed */
   clock_t  si_stime;     /* System time consumed */
…
}
  •  sa_mask : 차단할 신호의 집합입니다. sigprocmask를 통해서 특정 신호를 BLOCK 시킬지, 말지를 정합니다.
  •  sa_flags : 신호 옵션들입니다. 아래와 같은 옵션들이 존재합니다. 
SA_NOCLDSTOP signum이 SIGCHLD인 경우 자식 프로세스가 정지되었을때, notification을 받지 않습니다. 자식이 SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU 신호를 받아 정지되었을때 신호를 안받는다는 겁니다. 
SA_NOCLDWAIT signum이 SIGCHLD일때, 자식 프로세스가 종료되었을때 시스템이 좀비프로세스를 만들지 않게 합니다.
SA_NODEFER 신호가 잡혀서 신호 처리 함수가 실행되는 도중에 다시 같은 신호가 발생됐을때, 시스템이 자동으로 차단하지 않습니다.
SA_ONSTACK sigaltstack으로 대체 스택을 선언해두었다면 신호가 대안 스택의 프로세스에 전달됩니다.
SA_RESETHAND 신호 처리 함수에 진입할때 이 신호의 처리 방식을 SIG_DFL로 재설정하고 SA_SIGINFO 플래그를 지웁니다. 
SA_RESTART interrupt된 시스템 콜 호출이 자동으로 재시작됩니다. 아래 예에서 보겠습니다.
SA_RESTORER 어플리케이션에서 사용할 의도로 만들어진 flag가 아닙니다. sa_restorer와 관련된 옵션입니다.
SA_SIGINFO 신호 처리부에 추가적인 두가지 정보를 전달합니다. 이때 sa_sigaction함수 포인터를 설정해야합니다. 위의 sa_sigaction 인자에 대한 설명을 참고하세요.
  •  sa_restorer : 이 필드는 앱 사용 목적으로 만들어진 필드가 아닙니다. sigreturn과 관련된 필드라고 하네요. 넘어가겠습니다.

 

sigaction 코드 예제 - 자식 프로세스의 현재 상태 확인

시그널 핸들러를 이용해서 자식 프로세스가 종료하여 SIGCHLD를 발생했을 때 wait을 호출해서 자식 프로세스의 종료 상태를 알 수 있습니다. 그런데 자식 프로세스의 종료뿐만 아니라 정지, 재개 상태로 바뀌었을 때도 이러한 SIGCHLD를 발생시킨다고 했었습니다. 그렇다면 자식 프로세스가 종료할 경우에만 딱 wait할 수 있는 방법이 있을까요?

SA_SIGINFO 플래그와 siginfo_t의 si_code를 이용하면 됩니다.

//sigchld_info.c

#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>

void action(int signo, siginfo_t *info, void* context){
        if(signo == SIGCHLD){
                printf("pid:%d, uid:%d\n", info->si_pid, info->si_uid);
                if(info->si_code == CLD_EXITED){
                        pid_t child_process = wait(NULL);
                        printf("[parent] child process(%d) exit\n", child_process);
                        exit(0);
                }
                if(info->si_code == CLD_KILLED){
                        pid_t child_process = wait(NULL);
                        printf("[parent] child process(%d) killed\n", child_process);
                        exit(1);
                }
                if(info->si_code == CLD_STOPPED)
                        printf("[parent] child process stopped\n");
                if(info->si_code == CLD_CONTINUED)
                        printf("[parent] child process continued\n");
        } 
}
int main(){
        pid_t pid;
        struct sigaction act;
        act.sa_flags = SA_SIGINFO;
        act.sa_sigaction = action;

        if((pid = fork()) < 0){
                printf("fork error \n");
                return 1;
        } else if(pid == 0){  //child process
                int count = 0;
                while(count < 30) {
                        printf("\t[child] count:%d\n", count++);
                        sleep(1);
                }
                exit(0);
        } else { //parent process 
                printf("[parent] child process : %d\n", pid);
                sigaction(SIGCHLD, &act, NULL);
                while(1) pause();
        }

}

sigaction 구조체에 SA_SIGINFO를 이용해서 sa_sigaction 핸들러를 활용합니다. SA_SIGINFO를 사용하게 되면 sa_sigaction을 사용할 수 있다고 위 설명해서 말씀드렸죠?

struct sigaction act;
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = action;

action 함수에서 siginfo_t의 si_code는 SIGCHLD 신호가 발생했을 때 부가적인 정보가 담겨져있습니다. 그래서 보다 정교한 제어를 하게 됩니다. 

void action(int signo, siginfo_t *info, void* context)

30초간 자식의 상태를 바꿀 수 있습니다. Kill 명령어를 통해서 정지, 재개를 해보세요. 그리고 kill -SIGKILL을 통해서 비정상 종료도 해보시면 자식 프로세스의 상태를 더 자세히 확인할 수 있습니다.

terminal1 terminal2
# ./a.out
[parent] child process : 236739
...
               [child] count:6
pid:236739, uid:0
[parent] child process stopped
pid:236739, uid:0
[parent] child process continued
                [child] count:7
                [child] count:8
...
                [child] count:29
pid:236739, uid:0
[parent] child process(236739) exit
# kill -SIGTSTP 236739
# kill -SIGCONT 236739

 

si_code의 값은 발생한 시그널에 따라서 다르게 설정이 됩니다. 아래의 표를 참고하시기 바랍니다.

 

sigaction 코드 예제  - 시그널 함수 구현

아래는 sigaction함수를 통해서 signal함수를 흉내낸 코드입니다. 

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

typedef void Sigfunc(int);

Sigfunc* my_signal(int signo, Sigfunc *func){
        struct sigaction act,oact;
        act.sa_handler = func;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;

        if(sigaction(signo, &act, &oact) < 0)
                return SIG_ERR;
        return oact.sa_handler;

}
void sig_int(int signo){
        printf("sig_int start\n");
}

int main(){
        Sigfunc* origin;
        //그전의 신호 처리 함수 포인터가 origin에 저장됨. 지금은 SIG_DFL
        origin = my_signal(SIGINT, sig_int);
        printf("main start\n");
        //신호가 발생할때까지 대기
        pause();
        printf("main end\n");
}
main start
^Csig_int start      <-- Ctrl+C 입력
main end

 

뭔가 되는것같긴한데 문제점이 있습니다. 아래와 같이 main함수를 변경해봅시다. 

int main(){
        int n;
        char buf[64];
        my_signal(SIGINT,sig_int);
        if((n=read(0,buf,64)) < 0){
                printf("read failed\n");
        }else{
                printf("%s\n",buf);
        }

}

 

그리고 실행시켜보면 읽기가 실패했습니다. read하는 도중에 Ctrl+C를 눌러서 신호를 발생시켰고, 그때문에 신호 처리함수가 호출이 되었습니다. 이때 read는 interrupt되었지만, 다시 복구 되지 않고 있는 현상이 문제입니다.

^Csig_int start    <-- Ctrl + C 입력
read failed

그래서 SA_RESTART 플래그를 넣어서 시스템 콜이 재시작되도록 설정합니다.

Sigfunc* my_signal(int signo, Sigfunc *func){
        //... 중략
        act.sa_flags = 0;
        act.sa_flags |= SA_RESTART;
        //... 중략
}

이후의 실행은 아래와 같습니다.

hello^Csig_int start    <-- Ctrl + C 입력
world
world

 

hello를 입력하는 와중에 Ctrl+C를 입력시켜서 SIGINT신호를 발생시켰습니다. 그래서 sig_int start라는 출력문이 수행이되었고, 신호 처리 함수가 끝난 후 다시 read를 하게 됩니다. world를 출력하고 엔터를 치면 world만 출력이 되네요.

다시 read를 호출했기 때문입니다. 

지금까지 sigaction에 대한 설명과 sigaction을 활용한 예제 2가지를 보았습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

리눅스에 대한 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

오류 처리(Error Handling)

우리가 open으로 파일을 열때 -1의 값을 돌려받는다면 오류가 발생한 것입니다. 그런데 -1만 가지고는 우리는 왜 오류가 발생했는지 알 수가 없죠. 접근 권한 부족이라던가, 그런 파일이 존재하지 않는다던가 말입니다. open과 관련된 오류는 15가지 정도나 됩니다. 이런 원인을 알 수만 있다면 문제를 해결하는데 큰 도움이 되겠죠. 그래서 이번 포스팅에서는 오류의 원인을 어떻게 쉽게 알아 낼 수 있는지 관련된 내용입니다.

errno

errno는 일종의 오류의 코드가 담긴 변수입니다. 이 변수를 활용하기 위해서는 우리는 errno.h라는 헤더파일을 include시켜줘야합니다.

#include <errno.h>

 

errno.h 파일에는 errno와 errno에 설정될 수 있는 에러 코드가 담겨있습니다. 에러 코드는 상수로 담겨있으며 그 종류가 매우 많아서 여기에서는 담지 않겠습니다. 여러분들이 에러 코드에 대한 정보를 직접 확인해보시는 것을 추천해드리며 리눅스 매뉴얼 페이지에 존재합니다. 아래의 명령을 통해서 메뉴얼 페이지를 확인해보세요.

# man 3 errno

 

혹은 errno 명령을 사용하시면 됩니다. 위 명령은 moreutils라는 페키지에 존재하기 때문에 없으면 설치해줍니다. 

# apt install moreutils

 

errno -l 명령어 실행시 

# errno -l
EPERM 1 명령을 허용하지 않음
ENOENT 2 그런 파일이나 디렉터리가 없습니다
ESRCH 3 그런 프로세스가 없음
EINTR 4 중단된 시스템 콜
EIO 5 입력/출력 오류
ENXIO 6 그런 장치 혹은 주소가 없음
E2BIG 7 인수 명단이 너무 김
ENOEXEC 8 Exec 형식 오류

에러를 나타내는 상수는 모두 앞에 E가 붙는 점을 확인하세요.

 

strerror

#include <string.h>
char *strerror(int errnum);

errno 가지고는 어떤 오류인지 사람이 직접적으로 확인하기가 어렵습니다. 코드를 직접 확인하여서 에러 메시지를 출력해야합니다. 이런 번거로움없이 strerror에 인자로 errno를 전달하게 되면 알아서 오류 메시지를 반환해줍니다. 아래는 아주 간략한 예제입니다.

#include <errno.h>
#include <string.h>
#include <stdio.h>

int main(){
        char *msg;
        errno = EPERM;
        msg = strerror(errno);
        printf("error :%s\n",msg);

}

 

출력을 보게 되면 EPERM에 대한 오류 내용을 볼 수 있습니다. 

error :Operation not permitted

 

perror

#include <stdio.h>
void perror(const char *s);

perror는 strerror과 비슷하게 오류의 내용을 문자열로 출력하여 줍니다. 이때 전달되는 문자열 s는 우리가 출력해주길 원하는 문자열이며, 오류 내용은 errno에 따라서 알아서 출력을 해줍니다.

아래는 예제 코드입니다. 

#include <errno.h>
#include <stdio.h>

int main(){
        errno = EPERM;
        perror("error 발생");
}

출력을 보면 errno를 전달하지 않았음에도 함수 내부에서 errno를 확인하기 때문에 에러를 알 수 있습니다. 에러 내용은 위의 strerror의 메시지와 같은 것을 알 수 있죠? 내부적으로 strerror을 사용하는 것을 알 수 있습니다. 

error 발생: Operation not permitted

perror는 이렇게 errno를 직접 명시적으로 넣어주지 않아도 되기 때문에 매우 편리합니다.

 

open error 처리 코드

한 예로 open에 대한 error처리는 아래와 같이 수행이 됩니다. 오직 root만이 읽을 수 있는 파일이 있습니다. 

$ ls -l
-r-------- 1 root   root       0  4월 14 17:08 root_file

 

그리고 root 권한이 없는 일반 사용자로 아래의 코드를 짜서 실행해보면 열리지 않겠죠. 결국 open은 -1을 반환하게 됩니다. 그리고 왜 열지 못하는지는 perror를 통해 확인해볼 수 있습니다.

//err.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>

int main(){
        int fd = open("root_file", O_RDONLY);
        if(fd < 0){
                perror("file open error");
                exit(-1);
        }
        return 0;
}
$ gcc err.c
$ ./a.out
file open error: Permission denied

 

지금까지 오류를 쉽게 확인할 수 있는 내용이었습니다. 사실 error에 대해서 처리해주는 것이 여간 귀찮은 것이 아닌데 이러한 오류 처리 함수를 통해서 번거로움을 줄여보시기 바랍니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

리눅스에 대한 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

alarm함수

alarm함수는 일정 초단위의 시간 후에 SIGALRM을 발생시키는 함수입니다. 이 시그널이 발생하게 되면 기본 동작은 프로세스 종료입니다. 

혹시 시그널에 대해서 모르시나요? 아래의 포스팅을 참고하며 보시기 바랍니다.

https://reakwon.tistory.com/46

 

[리눅스] 시그널 (SIGNAL)1 시그널 다루기(시그널 핸들러)

시그널 의미전달에 사용되는 대표적인 방법은 메세지와 신호입니다. 메세지는 여러가지 의미를 갖을 수 있지만 복잡한 대신 , 신호는 1:1로 의미가 대응되기 때문에 간단합니다. 신호등이 가장

reakwon.tistory.com

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

seconds : 신호가 발생할 때까지의 클록 초를 의미합니다. 이 시간이 경과가 되고다면 커널이 신호를 발생시키게 됩니다. 만약에 alarm에 0을 전달하게 되면 alarm함수는 알람 발생을 취소하게 됩니다.

반환 : 여기서 주의해야할 점은 하나의 프로세스가 작동시킬 수 있는 알람 시계는 오직 하나뿐이라는 점입니다. 이전에 프로세스가 등록해 놓은 알람이 있다면 alarm함수는 이전에 등록되어있던 알람의 남은 시간(초)를 반환합니다. 만일 알람이 지정되어있지 않은 새로운 알람을 등록하는 것이라면 반환 값은 0이 됩니다. 아래의 심플한 코드와 결과를 보고 어떻게 동작이 되는지 알 수 있습니다.

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

int main(){
        //첫번째 알람으로 ret = 0
        int ret = alarm(10);
        printf("처음 등록한 알람 - 남은 시간:%d\n", ret);
        //첫번째 알람 끝나기도 전에 두번째 알람 등록
        ret = alarm(5);
        printf("두번째 등록한 알람 - 남은 시간 :%d\n",ret);

}
처음 등록한 알람 - 남은 시간:0
두번째 등록한 알람 - 남은 시간 :10

 

그런데 위의 코드에서는 alarm이 울리기 전에 코드가 모두 종료가 되기때문에 알람이 실제 울리는지 안울리는지 알수가 없는 코드가 됩니다. 아래의 pause함수도 같이 사용합시다.

 

pause

#include <unistd.h>
int pause(void);

이 함수는 신호를 기다리는 함수입니다. 이 함수가 수행이 되면 프로세스는 신호가 발생될때까지 sleep상태에 빠지게 됩니다. 위에서 언급한 SIGCHLD 외에 다른 신호도 기다립니다. 

반환 : 시그널 핸들러가 처리부를 실행하고 나서 시그널 핸들러가 반환될 경우에 이 함수가 반환됩니다. 이때 pause함수는 errno를 EINTR로 설정하고 -1을 반환합니다. 

다시 말해서 signal함수에 전달되는 시그널을 처리하는 함수가 있죠? 그 함수부터 반환되고 난 이후에 pause가 끝난다는 이야기입니다. 아래의 코드를 보게되면 signal함수로 signal_handler하는 시그널 핸들러를 등록해줍니다. 이후 pause로 신호가 발생될때까지 기다리게 됩니다. 신호가 발생하게 되면 1. signal_handler의 함수가 수행하고, 2. pause()가 반환하면서 3. pause() 이후의 코드가 수행된다는 뜻입니다.

//signal handler 등록
if(signal(SIGALRM, signal_handler) == SIG_ERR) return seconds;
// ... 수행부 ..//
pause();

 

sleep함수 구현

지금까지 소개한 alarm과 pause함수로 간단한 sleep()함수를 구현해볼 수가 있습니다. sleep함수는 원래 unistd.h에 포함되어 있는 함수이고 사용자가 지정한 초수만큼 대기(sleep상태)가 됩니다. 

#include <unistd.h>
unsigned int sleep(unsigned int seconds);

구현

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
static void sig_alarm(int signo){
        printf("signo:%d\n",signo);
}

unsigned int my_sleep(unsigned int seconds){
        if(signal(SIGALRM, sig_alarm) == SIG_ERR)
                return seconds;
        int ret = alarm(seconds);
        //이미 alarm이 존재하면 존재하는 alarm 삭제 후 다시 등록
        if(ret < seconds){
                alarm(0);       //취소
                alarm(seconds); //새등록
        }
        pause();
        return alarm(0);
}
int main(){
        printf("before my_sleep\n");
        //3초간 sleep
        my_sleep(3);
        printf("after my_sleep\n");

}

 

 

이미 sleep 중인데, 다시 sleep이 호출되게 되면 구현의 편의를 위해서 기존 sleep을 취소하고 새로운 sleep으로 교체하도록 하였습니다. 사실 이전의 sleep까지 기다리고 난 이후에 새로운 sleep을 해주어야합니다. 

보완

여기서 pause 호출전에 alarm이 먼저 호출하게 되면 pause는 다른 신호가 잡히기 전까지는 영원히 대기하게 됩니다. 이를 보완하기 위해서 setjmp를 활용할수 있습니다.

setjmp를 잘 모르신다면 setjmp와 longjmp와 관련해서는 아래의 포스팅을 참고하시기 바랍니다.

https://reakwon.tistory.com/211

 

[리눅스] 단번에 이해하는 setjmp, longjmp를 활용하는 방법

비국소(nonlocal) 분기 setjmp나 longjmp는 이름에서도 알수 있듯이 jump하는 함수입니다. 실행부를 점프한다는 것입니다. 그전에 비국소(nonlocal)라는 단어를 설명할 필요가 있습니다. C언어에서 goto구문

reakwon.tistory.com

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <setjmp.h>
static jmp_buf buf;
static void sig_alarm(int signo){
        longjmp(buf,1);
}

unsigned int my_sleep(unsigned int seconds){
        if(signal(SIGALRM, sig_alarm) == SIG_ERR) return seconds;

        if(setjmp(buf) == 0){
        	    //pause호출 되기전 SIGALRM이 호출된다면? 
                int ret = alarm(seconds); 
                if(ret < seconds){
                        alarm(0);
                        alarm(seconds); 
                }
                pause();
        }
        return alarm(0);
}
int main(){
        printf("before my_sleep\n");
        //3초간 sleep
        my_sleep(3);
        printf("after my_sleep\n");

}

 

만약 SIGALRM이 pause 호출 전에 발생하게 된다면, sig_alarm이 호출이 되고 longjmp에 의해 setjmp쪽으로 이동이 되겠죠. setjmp의 반환값은 1이 되기 때문에 if(setjmp(buf) == 0)을 수행하지 않고 빠져나오게 됩니다. 그래서 my_sleep함수가 끝나게 되죠. 이렇게 pause가 무한히 신호를 기다리지 않게 되기 때문에 처음 my_sleep() 구현에 문제점을 해결할 수가 있습니다.

그럼에도 불구하고 여전히 다른 문제점들이 존재하기는 합니다. 이 함수를 호출하는 프로세스에서 signal함수를 통해서 다른 시그널 핸들러를 등록하게 되면 안좋은 결과가 생길 수 있습니다.

하지만 alarm과 pause를 어떻게 사용하는지에 대한 기본적인 개념을 알기 위한 코드이니, 문제점은 여러분들이 해결해보시기 바랍니다.

지금까지 alarm함수와 pause함수에 대해서 알아보았고 이것을 활용하는 sleep함수까지 살짝 맛보았습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

fork, exec에 대한 상세 내용과 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

system함수

system함수는 유닉스 운영체제에는 모두 지원합니다. system함수는 입력받은 command의 문자열을 실제로 실행시켜주는 함수입니다.

system함수를 사용하기 위해서는 stdlib.h 헤더파일을 include 해야합니다.

#include <stdlib.h>

system함수의 원형은 아래와 같습니다.

int system(const char *command);

사용하는 방법은 매우 간단합니다. command에 실행할 명령어를 전달해주기만 하면 됩니다. 아래의 사용 예를 보시면 금방 사용하실수 있을겁니다.

사용예)

//system_test.c
#include <stdlib.h>
#include <stdio.h>

int main(){
        char *command="ls -al";
        int ret;
        ret = system(command);
        printf("system함수 종료 :%d\n",WEXITSTATUS(ret));
}
# gcc system_test.c
# ./a.out
합계 208
drwxr-xr-x 19 ubuntu ubuntu  4096  4월 11 17:22 .
drwxr-xr-x  6 root   root    4096  4월  1 15:38 ..
-rw-------  1 ubuntu ubuntu   378  4월 11 17:17 .Xauthority
-rw-------  1 ubuntu ubuntu  5496  4월 11 12:42 .bash_history
-rw-r--r--  1 ubuntu ubuntu   220  2월 22  2021 .bash_logout
...
system 함수 호출 완료 ret:0

 

system함수의 내부

system함수에 NULL을 전달하게 되면 적절한 명령처리기가 존재한다면 0을 돌려줍니다. 그 외에는 상황에 따라 다릅니다.  system함수를 내부적으로 들여다보면 fork, exec, waitpid로 이루어진 함수입니다. 이 세개의 함수에 대해서 모르신다면 아래의 포스팅을 참고하시기 바랍니다.

- fork()

https://reakwon.tistory.com/45

 

[리눅스] 프로세스 생성과 특징, 종료 (fork, wait), 예제 코드

프로세스(process) 프로세스는 간단히 말하자면 실행 중인 프로그램을 의미합니다. 아마 여러분들은 컴퓨터를 하면서 아주 빈번하게 듣는 용어이기도 합니다. 실행 중인 프로그램이라?? 컴퓨터에

reakwon.tistory.com

- exec()

https://reakwon.tistory.com/207?category=300674 

 

[리눅스] exec류의 함수 사용방법,간단한 쉘구현 -execl,execv,execve...

exec famliy exec~로 되는 함수들이 리눅스에 몇가지 존재하고 있습니다. 리눅스에서는 exec family라고 부릅니다. 이 함수들은 모두 공통적으로 프로그램을 실행한다는 특징을 갖고 있습니다. 그 함수

reakwon.tistory.com

- waitpid

https://reakwon.tistory.com/99

 

[리눅스] 조건변수를 통한 스레드 동기화 설명, 예제(pthread_cond_wait, pthread_cond_signal)와 생산자-소

조건 변수 조건 변수를 설명하기 전에 다음과 같은 상황이 발생했다고 칩시다. 먼저 스레드 2개가 존재합니다. 저는 짧은 코드를 좋아하므로 아주 간단한 역할을 하는 2개의 쓰레드를 생성했습

reakwon.tistory.com

 

 

[리눅스] 조건변수를 통한 스레드 동기화 설명, 예제(pthread_cond_wait, pthread_cond_signal)와 생산자-소

조건 변수 조건 변수를 설명하기 전에 다음과 같은 상황이 발생했다고 칩시다. 먼저 스레드 2개가 존재합니다. 저는 짧은 코드를 좋아하므로 아주 간단한 역할을 하는 2개의 쓰레드를 생성했습

reakwon.tistory.com

 

system함수의 반환 값

1. fork 호출이 실패했거나 waitpid가 EINTR외의 오류를 돌려주면 system함수는 errno를 EINTR오류로 설정하고 -1를 반환합니다.

2. exec함수가 실패했다면 이런 경우에는 shell을 실행할수 없다는 뜻이며, exit(127)과 동일합니다. 

3. 그 외의 경우에는 waitpid에 지정된 셸의 종지 상태가 return됩니다.

 

아래의 코드는 system함수를 흉내낸 코드입니다. 

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

int system(const char *cmd){
        pid_t pid;
        int status;

        if(cmd == NULL) return 1;       //UNIX에는 명령 처리기가 존재
        if((pid = fork()) < 0){
                status = -1;    //프로세스 생성 에러
        }else if(pid == 0){     //자식 프로세스
                execl("/bin/sh","sh","-c",cmd,(char*)0);
                _exit(127);		//위 2번읜 case
        }else{                  //부모 프로세스 : 자식이 끝날때까지 기다림
                while(waitpid(pid, &status, 0) < 0){
                        if(errno != EINTR){	//위 1번의 case
                                status = -1;
                                break;
                        }
                }
        }
        return status;
}
int main(){
        int ret;
        ret = system("ls -al");
        printf("system함수 종료 :%d\n",WEXITSTATUS(ret));
}

 

이러한 구현사항때문에 내부적으로 fork()로 자식 프로세스를 수행하고 자식 프로세스는 exec함수를 호출하는데요. 부모 프로세스는 waitpid로 자식 프로세스를 기다리기 때문에 system다음 줄의 printf가 실행될수 있는 것이죠.

 

종지 상태 확인

WEXITSTATUS로 실제 exit()이나 return값을 확인할수 있습니다. 아래는 main에서 바로 return 18로 빠져 나오는 한 프로그램입니다. 혹은 exit(18)을 해도 똑같습니다.

//program.c
int main(){
        //exit(18);
        return 18;
}
# gcc program.c -o program
# ls program
program

program이라는 실행파일이 생겨납니다. 이제 이 실행파일을 실행시키기 위해 system함수를 사용해보겠습니다.

//system_test.c
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(){
        int ret;
        ret = system("./program");
        printf("system함수 종료 :%d\n",WEXITSTATUS(ret));
}
# gcc system_test.c
# ./a.out
system함수 종료 :18

 

우리가 return했던 값을 확인할 수 있죠? 단순 ret값을 출력하는게 아닌 매크로를 통해서 종지상태를 확인해야한다는 점을 기억하세요. 

 

이상으로 system에 관한 포스팅을 마치도록 하겠습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

아래의 내용과 더불어 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

비국소(nonlocal) 분기

setjmp나 longjmp는 이름에서도 알수 있듯이 jump하는 함수입니다. 실행부를 점프한다는 것입니다. 그전에 비국소(nonlocal)라는 단어를 설명할 필요가 있습니다. C언어에서 goto구문을 아시죠? goto도 역시 코드를 이동시킬 수 있지만, goto와는 다릅니다. 아래는 간단한 goto의 활용예입니다.

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

int go_to(){
        int i = 1;

        if(i==1)
                goto here;

        here:{
                printf("here!\n");
        }
}
int main(){
        go_to();
        return 0;
}

실행해보면 here label이 있는곳으로 코드가 이동되어 실행이되는것을 볼 수 있습니다.

label은 항상 goto가 부르는 label과 같은 함수 내에 있어야합니다. 즉, 아래와 같은 코드는 컴파일되지 않습니다. 다른 함수로 건너뛸수가 없다는 말입니다.

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

int go(){
    here :
        printf("here!\n");
    go_to();
}
int go_to(){
    int i = 1;
    if(i==1)
        goto here;

}
int main(){
    go();
    return 0;
}
$ gcc jump.c
jump.c: In function ‘go’:
jump.c:7:2: warning: implicit declaration of function ‘go_to’ [-Wimplicit-function-declaration]
  go_to();
  ^
jump.c: In function ‘go_to’:
jump.c:12:3: error: label ‘here’ used but not defined
   goto here;

 

setjmp와 longjmp는 비국소(nonlocal) 분기를 수행합니다. 비국소라는 같은 함수내부에서만 점프하지는 않는다는 것입니다. 함수가 호출된 경로 안에 있는 다른 어떤 함수로 점프하는 것이 가능하다는 얘기입니다.

 

setjmp, longjmp

#include <setjmp.h>

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

setjmp, longjmp 를 사용하기 위해서는 setjmp.h를 include시켜야합니다. 아래의 코드에서 보겠지만 이 함수들에 대해서 간단하게 설명하면 이렇습니다.

setjmp : 점프할 위치를 지정합니다. env는 나중에 longjmp가 호출되었을때 스택의 상태를 복원할때 필요한 모든 정보를 담는 배열입니다. setjmp의 반환값은 longjmp의 val입니다. 이 반환값으로 흐름을 분기시킬 수 있습니다.

longjmp : 실제로 점프하는 함수입니다. 이 함수를 통해서 setjmp위치로 돌아갑니다. 이때 env는 setjmp와 같은 env를 사용하며 val은 setjmp가 반환할 값입니다. 

이 함수들을 이용해서 다음의 프로그램을 수정할것인데요. 프로그램에 대해서 설명하자면 비밀번호를 입력받고 입력이 8자리인가, 포함되어서는 안될 문자가 있는가 검사하는 아주 간단한 프로그램입니다.

#include <stdio.h>
#include <string.h>
#define TRUE 1
#define FALSE 0
char password[32];

int check_size(){
        if(strlen(password)< 8) return FALSE;
}
int is_possible_word(){
        int i;
        for(i=0;i<strlen(password);i++)
                if(password[i] == '|' || password[i] == '%' || password[i] == '&') return FALSE;
        return TRUE;
}
int verify_input(){
        if(!check_size()) return 1;
        if(!is_possible_word()) return 2;
        return 3;
}
int main(){

        while(1){
                printf("비밀번호:");
                scanf("%s",password);
                int result = verify_input();
                if(result == 3) break;
                switch(result){
                        case 1:
                                printf("비밀번호는 8자 이상이어야합니다.\n");
                        break;
                        case 2:
                                printf("|, %%, &문자는 사용할 수 없습니다.\n");
                        break;
                }
        }
        return 0;
}

 

위 코드의 문제점을 파악해보도록 하지요.

1. 검증이 완료될때까지 while문으로 계속 password를 입력받습니다. 

2. verify_input에는 두가지 정도를 검증하는데 하나는 길이, 다른 하나는 포함불가한 문자를 거르는것인데요. 이때 하나의 조건이라도 맞지 않는다면 verify_input에 그에 맞는 error code를 주고, verify_input은 다시 main으로 error code를 반환하게 됩니다. 만약 검증해야될 조건이 많다면 더 복잡해지겠네요. 

이 프로그램의 특징은 비밀번호의 조건중 하나라도 맞지 않으면 처음부터 다시 수행합니다. 그냥 main 함수 밑에 점프할 곳을 딱 정해놓고 check_size()나 is_possible_word()에 그 조건이 맞지 않는다면 그쪽으로 점프시키는 쪽으로 변경하면 안될까요? 이렇게 되면 while을 쓰지 않아도 되고, verify_input()에 굳이 error code를 전달하지 않아도 되니까요. 만약 verify_mode()에서 is_possible_word()까지 들어가고 여기에 새로운 조건을 검사하는 함수가 추가되서 계속 호출되면 코드는 더 복잡해집니다. 이러한 상황은 곧 setjmp, longjmp를 통해서 간단하게 해결할 수 있습니다.

그 코드가 아래에 있습니다. 

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

#define TRUE 1
#define FALSE 0

//setjmp와 longjmp가 같이 사용하는 jmp_buf로
//전역적으로 사용
jmp_buf jmpbuf;
char password[32];

void check_size(){
        if(strlen(password)< 8) 
                longjmp(jmpbuf, 1);
}

void is_possible_word(){
        int i;
        for(i=0;i<strlen(password);i++){
                if(password[i] == '|' || 
                                password[i] == '%' || password[i] == '&') 
                        longjmp(jmpbuf,2);
        }
}

void verify_input(){
        check_size();
        is_possible_word();
}

int main(){
        int ret = 0;
        //setjmp는 나중에 longjmp를 통해서 점프되었을때실행되는 부분
        if((ret = setjmp(jmpbuf)) > 2){
                printf("unknown option\n");
                return -1;
        }else{
                //longjmp에서 두번째 인자 val이 ret값이 됨.
                switch(ret){
                        case 1:
                                printf("비밀번호는 8자 이상이어야합니다.\n");
                                break;
                        case 2:
                                printf("|, %%, &문자는 사용할 수 없습니다.\n");
                                break;
                }
        }

        printf("비밀번호:");
        scanf("%s",password);

        verify_input();
        return 0;
}

 

메인의 while문이 빠졌고, check_size(), is_possible_word()에서 직접 longjmp를 통해서 main함수의 입력받는 부분으로 되돌아갑니다. setjmp의 반환값은 longjmp의 두번째로 전달되는 인자의 값인것을 알 수 있습니다.

 

만약 setjmp 이전에 longjmp를 호출하게 되면 Segmentation fault가 발생하게 됩니다. 그러니까 아래와 같은 상황에서는 점프할 수 없습니다. 점프는 함수가 호출된 순서에서 거꾸로 돌아가는 것(원복의 목적)만 가능합니다.

 

#include <stdio.h>
#include <setjmp.h>
jmp_buf jmpbuf;
int main(){
    int ret = 0;
    longjmp(jmpbuf, 1); //이 코드는 수행할수 없다.

    setjmp(jmpbuf);
    return 0;
}

 

사실 스택 프레임을 거꾸로 돌린다고 표현하는게 더 정확할 것 같네요. 여기에 위 코드의 동작과정을 그림으로 표현하였습니다. check_size()에서 longjmp를 호출했을때의 상황입니다.

check_size()에서 longjmp

 

즉, 스택을 복원하면서 점프하는 것입니다. 여기서 정적변수, 전역변수, 휘발성 변수는 복원되지 않습니다. 정적변수, 전역변수는 스택 메모리에 기억되는 값이 아니니까요.  메모리의 구조를 확인하려면 아래의 포스팅을 확인해보세요.

https://reakwon.tistory.com/20

 

[C언어] 동적 메모리 할당의 세가지 방법 malloc, calloc, realloc

동적 메모리 할당 우리는 이제껏 메모리를 할당할때 정적으로 할당했습니다. 어떤 것이냐면 int arr[100]; 이렇게 할당을 했었죠. 뭐 문제없습니다. 실행도 잘 되구요. 하지만 이런 상황은 조금 불

reakwon.tistory.com

 

지금까지 setjmp, longjmp를 통해서 비국소적 분기를 이해해보았습니다. 

반응형
블로그 이미지

REAKWON

와나진짜

,

메시지큐, 공유메모리 등 더 많은 정보와 예제코드를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

Message Queue

프로세스 간 통신 방식인 IPC기법 중 하나인 메시지 큐는 두 가지 사용 방법이 있습니다. msgget, msgsend, msgrecv와 같은 함수를 사용하는 방식인 System V 방식의 메시지큐, 그리고 지금 알아볼 mq_open, mq_send, mq_recv와 같은 함수를 사용하는 방식인 POSIX 방식이 있습니다. 

System V의 메시지 큐를 사용하는 방법과 예제를 보시려면 아래의 포스팅을 방문하여 확인해보세요.

https://reakwon.tistory.com/97

 

[리눅스] 메시지 큐(message queue) 개념과 예제(msgget, msgsnd, msgrcv, msgctl)

메시지 큐 IPC기법 중 하나인 메시지큐는 Data Structure 중 큐를 사용합니다. 기본적으로는 먼저온 메시지가 먼저 꺼내어집니다. 메시지큐의 msgtype에 따라 특정 메시지 중 가장 먼저들어온 메시지도

reakwon.tistory.com

 

System V는 옛날 유닉스 방식으로 보시면 되구요. 이후에 나온것이 POSIX입니다. POSIX는 Portable Operating System Interface라고 해서 이식 가능 운영체제 인터페이스라고 합니다. 리눅스가 POSIX를 준수하는 운영체제이구요. 

우선 다음의 헤더파일들이 필요합니다. 

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <mqueue.h>

 

컴파일할때는 -lrt로 링크를 걸어줘야하구요. 아래에서 코드를 실행하고 컴파일할때 다 설명할겁니다.

1) mq_open

mqd_t mq_open(const char *name, int oflag);
mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr);

 

메시지 큐를 여는 함수로 2개가 존재하네요. mode와 메시지 큐의 속성을 정의하는 attr도 같이 넘겨줄수 있는 함수가 따로 존재합니다. 

name : 메시지 큐 이름을 지정합니다.

oflag : 메시지큐에 옵션을 줄 수 있습니다. 아래와 같은 flag들이 존재합니다. 아래의 flag를 쓰려고 fcntl.h 파일을 include해야하는 겁니다.

flag 설명
O_RDONLY 읽기(메시지 받기:recv) 전용으로 메시지 큐를 엽니다. 
O_WRONLY 쓰기(메시지 전송:send) 전용으로 메시지 큐를 엽니다.
O_RDWR 읽기, 쓰기 전용으로 메시지 큐를 엽니다.
O_CLOEXEC close-on-exec이라고 하여, exec시 메시지큐를 닫습니다.
O_CREAT 메시지 큐가 존재하지 않으면 메시지큐를 생성합니다. 이 플래그를 사용하려면 두가지 인자가 추가로 더 필요한데 그게 바로 mode와 attr입니다.
O_EXCL O_CREAT과 같이 사용하여 이미 파일이 존재하면 EEXIST에러를 발생시킵니다.
O_NONBLOCK 비블록 형식으로 메시지큐를 엽니다. mq_send나 mq_receive시에 메시지가 없으면 지속적으로 기다리게 되는데, 이 플래그를 사용하면 기다리지 않습니다.

 

mode : O_CREAT과 같이 사용합니다. 메시지큐를 만들때 권한을 설정합니다.

attr : mq_attr의 포인터로 다음과 같이 정의되어있습니다. 메시지큐의 속성을 지정하는데, 최대 메시지수, 최대 메시지 사이즈(바이트) 등을 정할 수 있습니다.

 struct mq_attr {
       long mq_flags;       /* Flags (ignored for mq_open()) */
       long mq_maxmsg;      /* Max. # of messages on queue */
       long mq_msgsize;     /* Max. message size (bytes) */
       long mq_curmsgs;     /* # of messages currently in queue
                                       (ignored for mq_open()) */
};

반환 : 만약 메시지큐가 올바르게 생성되었다면 메시지큐를 제어할 수 있는 mqd_t가 반환됩니다. 우리는 이것을 통해서 메시지를 주고 받을 수 있습니다.

 

2) mq_send, mq_timedsend

int mq_send(mqd_t mqdes, const char *msg_ptr,nsize_t msg_len, unsigned int msg_prio);

#include <time.h>
int mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio,
                     const struct timespec *abs_timeout);

 

메시지를 보내는 함수는 두가지가 존재합니다. 

mqdes : mq_open시에 받은 mqd_t입니다. 

msg_ptr : char형 메시지 포인터입니다. 메시지 내용을 의미합니다.

msg_len : 이름에서 알 수 있듯이 메시지의 크기를 의미합니다.

msg_prio : 메시지의 우선순위를 지정합니다.

abs_timeout : 지정된 시간동안 메시지 전송을 보류합니다. 큐가 꽉 찼을 경우가 있을 경우에 말이죠. NON_BLOCK 플래그가 없어야합니다. timespec 구조체는 아래와 같습니다. 이 구조체를 사용하기 위해서 time.h가 필요합니다.

struct timespec {
       time_t tv_sec;        /* seconds */
       long   tv_nsec;       /* nanoseconds */
};

 

반환 : 성공시 0, 실패시 -1을 반환합니다.

 

3) mq_receive, mq_timedreceive

ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio);

#include <time.h>
ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio,
                          const struct timespec *abs_timeout);

 

자, 메시지를 보냈으니까 받아야겠죠. mq_send와 받는다는 것 외에는 모두 똑같습니다. msg_ptr에 메시지가 들어오게 됩니다. 그리고 mq_timedreceive를 통해서 역시 메시지가 쌓일때까지 지정된 시간만큼 기다릴수 있습니다.

반환 : 만약 올바르게 받았다면 실제로 받은 메시지의 사이즈가 반환되고 실패할 경우 -1이 반환됩니다. 

혹시 Message too long 에러가 발생한다면, 이것은 mq_receive 계열 함수에서 발생할 수 있는 에러인데, mq_receive의 man 페이지에서는 아래와 같이 설명하고 있습니다. mq_receive의 msg_len의 값은 attribute에서 설정한 mq_msgsize보다 작으면 안됩니다. (msg_len >= mq_msgsize)

 EMSGSIZE
              msg_len was less than the mq_msgsize attribute of the message queue.

 

4) mq_close

int mq_close(mqd_t mqdes);

 

mqdes : 닫을 mq를 지정합니다.

반환 : 성공시 0, 실패시 -1을 반환합니다. 

 

mq 기본예제

다음의 server.c는 메시지큐를 열고 받은 메시지를 출력하는 아주 간단한 역할을 합니다.

server.c

#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
#include <mqueue.h>
#include <sys/stat.h>

int main()
{
    struct mq_attr attr;
    //attr 때문에 에러 발생할 수 있음
    attr.mq_maxmsg = 20;
    attr.mq_msgsize = 128;
    char buf[128] = {0,};

    mqd_t mq;

    mq = mq_open("/reakwon_mq", O_RDWR | O_CREAT,  0666, &attr);
    if (mq == -1)
    {
            perror("message queue open error");
            exit(1);
    }

    if((mq_receive(mq, buf, attr.mq_msgsize,NULL)) == -1){
            perror("mq_receive error");
            exit(-1);
    }
    printf("mq received : %s\n", buf);
    mq_close(mq);
}

 

그리고 client.c는 메시지큐를 열고 메시지를 보내는 아주 간단한 프로그램이죠.

 

client.c

#include <stdlib.h>
#include <fcntl.h>
#include <mqueue.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
    struct mq_attr attr;
    //attr때문에 에러가 발생할 수 있음
    attr.mq_maxmsg = 20;
    attr.mq_msgsize = 128;
    mqd_t mq;
    char buf[128] = {0,};

    mq = mq_open("/reakwon_mq", O_WRONLY, 0666, &attr);
    if(mq == -1){
        perror("open error");
        exit(0);
    }

    scanf("%s", buf);
    if((mq_send(mq, buf, strlen(buf), 1)) == -1){
            perror("mq_send error");
            exit(-1);
    }
    mq_close(mq);
}

 

server.c와 client.c의 메시지큐 이름은 같아야합니다. 그리고 아래와 같이 컴파일해보도록 하죠. 아까 -lrt로 링크걸어줘야한다고 했죠?

# gcc server.c -o server -lrt
# gcc client.c -o client -lrt

 

그리고 server를 백그라운드로 실행시키고, client를 실행하여 메시지를 보내려고 하는 순간, 리눅스 세계는 그렇게 호락호락하지가 않죠. 아마도 아래의 에러 메시지가 발생할 수가 있습니다.

Invalid argument!

 

리눅스 시스템마다 message queue의 메시지 최대 사이즈라던가 큐의 크기가 정해져있는 한계(limit)이 있습니다. 어떻게 볼 수 있을까요? 아래의 경로에서 파일을 cat으로 확인할 수 있습니다.

/proc/sys/fs/mqueue/

 

여기서 메시지큐의 최대 크기와 메시지의 최대 크기를 볼까요? 

# cat /proc/sys/fs/mqueue/msgsize_max
8192
# cat /proc/sys/fs/mqueue/msg_max
10

 

위처럼 각 메시지의 최대 사이즈는 8192, 큐의 최대 들어갈 수 있는 메시지는 10개 입니다. 그래서 invalid argument 에러가 발생한 건데, 방법은 두 가지 입니다. 1. attr을 아래와 같이 시스템 limit에 맞게 수정하던지, 아니면 2. /proc/sys/fs/mqueue 안의 파일 내용을 강제로 고치던지 말이죠. 

1. attr 수정

attr.mq_maxmsg = 10;
attr.mq_msgsize = 8192;
char buf[8192] = {0,};

 

2. 루트 권한으로 아래 명령어 수행

echo 20 > /proc/sys/fs/mqueue/msg_max

 

이후에 컴파일 후 다시 수행해보시기 바랍니다.

# ./server &
[3] 17126
# ./client
hello?
mq received : hello?

 

서버는 받은 메세지를 정상적으로 출력하고 있네요. 

그런데, 메시지 큐는 어디에 실제로 생성이 될까요? 메시지큐가 실제 생성된 경로는 /dev/mqueue/ 하위에 mq_open시에 지정했던 mq 이름으로 존재하고 있습니다. 알아두는게 좋겠죠?

 

server.c의 코드를 아래와 같이 변경해봅시다. 아래의 코드는 mq_timedreceive를 통해서 일정 10초간 큐에 데이터가 없으면 곧장 종료하게 됩니다. 


#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
#include <mqueue.h>
#include <sys/stat.h>
#include <time.h>

int main()
{
    struct mq_attr attr;
    attr.mq_maxmsg = 20;
    attr.mq_msgsize = 128;
    char buf[128] = {0,};

    mqd_t mq;
    struct timespec tm;

    mq = mq_open("/reakwon_mq", O_RDWR | O_CREAT,  0666, &attr);
    if (mq == -1)
    {
            perror("message queue open error");
            exit(1);
    }

    clock_gettime(CLOCK_REALTIME, &tm);
    tm.tv_sec += 10;    //현재 시간 + 10초
    if(mq_timedreceive(mq, buf, attr.mq_msgsize, 0, &tm) == -1){
            perror("mq_receive error");
            exit(-1);
    }
    printf("mq received : %s\n", buf);
    mq_close(mq);
}

 

컴파일하고 서버를 실행시켜 10초간 대기해보세요. 아래와 같이 종료하게 됩니다.

# ./server
mq_receive error: Connection timed out

 

단, 그전에 클라이언트를 실행해서 큐에 데이터가 쌓여있다면 그 큐에 있던 데이터가 출력이 됩니다. 

여기까지 POSIX의 메시지 큐 활용에 대해서 알아보았습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

umask

우리가 유닉스 시스템에서 파일을 만들때 저처럼 별 생각없이 만드시는 분이 있을거라고 생각합니다. 파일 혹은 디렉토리를 생성할때 권한은 어떻게 결정이 될까요? 다음은 저의 리눅스에서 파일과 디렉토리를 생성했을때 어떤 권한을 가지고 있는지 확인해보겠습니다.

 

파일은 권한이 644, 디렉토리는 755로 설정이 되어있네요. 리눅스에서 원래 파일은 0666, 디렉토리는 0777로 생성되게 됩니다. 

644, 755?

읽기, 쓰기, 실행 권한은 숫자로 표현
할 수가 있습니다. 각 권한은 비트로 대응되어 설정되어있으면 1, 아니면 0이 됩니다. 그래서 읽기, 쓰기만 권한이 설정되어있고, 실행권한이 없다면 110이 되어서 10진수로 읽으면 6이 됩니다. 
그래서 소유자, 그룹, 다른 사용자 권한까지 포함이 되면 세글자의 10진수로 표현이 될 수 있습니다. 
666이라면 소유자, 그룹, 다른 사용자가 모두 읽기, 쓰기가 허용됩니다.
네 자리로 0666으로 표현할 수 있는데, 앞 숫자는 setuid, setgid, sticky 비트의 표현이 됩니다. 이 설명은 지금 포스팅에서 하지 않기로 합니다.

 

근데 위의 결과와는 다르네요? 네, 그것은 umask를 통해서 생성시 권한을 바꿔줄 수가 있기 때문이죠. umask 명령어를 그냥 쳐보면 현재 적용되어있는 umask의 값을 확인할 수 있습니다. 아래는 저의 리눅스의 umask값입니다.

 

0022입니다. umask가 적용되지 않았을때, 파일은 0666, 디렉토리는 0777 권한으로 생성되어진다고 했었죠? 그런데 지금 umask값은 0022이니까 0666에서 0222를 빼게 되면 0644, 0777에서 0022를 빼면 0755가 됩니다. 그래서 제가 아까 파일과 디렉토리를 생성했을때 0644, 0755의 권한으로 생성이 되었던 거죠.

생성 파일 권한
file  0666 - 0022 = 0644
dir  0777 - 0022 = 0755

 

umask를 해제하고 싶다면 umask 0으로 해제할 수 있습니다. 그렇기 때문에 아래와 같이 다시 파일과 디렉토리를 생성했을때 0666, 0777로 권한이 설정됩니다.

 

한번만 더 umask를 통해 생성시 권한을 변경시켜보도록 하겠습니다. 파일을 모두 읽기 권한만 설정하는 umask는 아래와 같습니다. 

umask를 사용할때 주의하셔야할 점은 권한을 설정해주는 것이 아니라 기본 권한(파일 : 0666, 디렉토리 :0777)에서 그 권한을 빼는 것을 기억해두시기 바랍니다.

 

chmod

chmod명령을 이용하면 디렉토리나 파일의 권한을 변경할 수 있습니다. 단, 변경하려는 파일이나 디렉토리의 소유자만이 가능합니다. 다음의 권한이 있는 파일이 있을때 소유자는 읽기,쓰기 그리고 그 외에는 읽기만 할 수 있도록 권한을 주고 싶다면 아래와 같이 권한을 변경할 수 있습니다. 

 

이렇게 숫자로 줄 수도 있고, 문자 약자로 더하거나(+) 뺄수(-)도 있습니다. 아래와 같이 말이죠.

반대로 뺄때는 - 기호를 사용하면 됩니다. 약자는 아래 표로 설명하도록 하겠습니다.

약자 표현 설명
u user로 파일 소유자를 의미합니다.
g group으로 파일 소유자의 그룹을 의미합니다.
o other로 다른 사용자를 의미합니다.
+, - 권한을 추가하려면 +, 빼려면 -를 사용하면 됩니다.
r read로 읽기 권한을 의미합니다.
w write로 쓰기 권한을 의미합니다.
x execute로 실행권한을 의미합니다.
s setuid, setgid 비트를 의미합니다.

 

위 표에 나와있는것 외에도 몇가지가 더 있습니다만, 잘 안써서 패스합니다

chmod를 설정할때 setuid, setgid, sticky 비트를 명시적으로 지정하지 않으면 그 전에 있던 suid, sgid, sticky 비트를 유지합니다. 

여기까지 파일 생성시에 권한과 권한 변경과 관련한 umask, chmod 명령어에 대해서 알아보았습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

exec와 관련한 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

exec famliy 

exec~로 되는 함수들이 리눅스에 몇가지 존재하고 있습니다. 리눅스에서는 exec family라고 부릅니다. 이 함수들은 모두 공통적으로 프로그램을 실행한다는 특징을 갖고 있습니다. 그 함수들이 어떤 것들이 있는지는 다음과 같습니다.

int execl(const char *pathname, const char *arg, .../* (char  *) NULL */);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg, .../*, (char *) NULL, char *const envp[] */);
int execve(const har* pathname, char *const argv[], char *const ecnp[]);
int execlp(const char *file, const char *arg, .../* (char  *) NULL */);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);

 

pathname은 경로이름을 인수로 받습니다. file은 파일이름을 받는데, 만약 /가 포함되어 있다면 경로명으로 간주하게 됩니다. /없이 파일이름을 쓰게 되면 PATH 환경 변수에 지정된 경로들에서 실행 파일을 찾아서 실행하게 됩니다. 

환경 변수를 출력해보시면 아래와 같이 echo를 통해서 확인할 수 있습니다.

# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:

또한 환경 변수에 경로를 추가할 경우에는 아래와 같이 export를 통해서 할 수 있습니다. 아래와 같이 /home을 환경변수에 추가해보았습니다.

# export PATH=$PATH:/home
# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/home

 

위의 모든 함수들을 외울수는 없지요. 그런데 규칙이 존재합니다. exec은 공통 접두사이고 그 다음 알파벳따라 정리한 표가 아래에 있습니다.

알파벳 설명
l list형식으로 arg0, arg1, ... , NULL로 인자를 전달하는 방식을 의미합니다.
v vector 형식으로 *argv[] 형태로 전달합니다. 끝에 NULL을 넣어줄 필요가 없습니다.
p 기본 환경 변수(PATH)의 경로를 삼습니다.
e environment를 입력받습니다. 

 

기본 사용법

아래의 코드는 모든 exec함수를 사용하는 간단한 예입니다. 따로 오류처리는 하지 않았습니다. 

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

int main(){
        char *arg1="-al";
        char *arg2="/etc";
        char *file = "ls";
        char *argv[]={file,arg1,arg2,NULL};
        char *path = "/bin/ls";

        printf("execl호출\n");
        execl(path, file, arg1, arg2, NULL);

        //printf("execv호출\n");
        //execv(path, argv);

        //printf("execle호출\n");
        //execle(path, file, arg1, arg2, NULL, NULL);

        //printf("execve호출\n");
        //execve(path, argv, NULL);

        //printf("execlp호출\n");
        //execlp(file, file, arg1, arg2, NULL);

        //printf("execvp호출\n");
        //execvp(file,argv);

        //printf("execvpe호출\n");
        //execvpe(file,argv,NULL);

        return 0;
}

 

명령어는 /bin/ls를 사용합니다. 그리고 첫번째 인자는 옵션 "-al"이며 두번째 인자는 내용을 출력할 디렉토리인 /etc입니다. argv를 잘 보시면 가장 첫번째 배열 원소는 실행할 파일 이름이 있다는 것 마지막은 NULL을 기억하세요. exec명령을 여러번 실행할 수는 없습니다. 그러니까 하나씩 실행하면서 결과를 확인해보세요. 

 

exec의 특징

exec를 사용하게 되면 기존의 exec를 실행시킨 프로세스는 exec가 실행한 프로그램으로 대체가 됩니다. 그렇기 때문에 exec를 실행시킨 프로세스 ID와 exec로 실행된 프로세스 ID와 같습니다. 실험해볼까요?

아래의 프로그램 코드는 exec로 실행시킬 프로그램입니다. 이 프로그램은 프로세스 ID, 부모 프로세스 ID, 세션 ID를 출력합니다.

//myprogram.c

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

int main(){
        printf("after exec\n");
        printf("pid : %d, ppid : %d\n",getpid(),getppid());
        printf("session id : %d\n", getsid(getpid()));
        return 0;
}

그리고 아래는 exec를 실행시킬 프로그램입니다. 하는 동작은 위의 내용과 같습니다.

//myexec.c
#include <unistd.h>
#include <stdio.h>

int main(){
        char *file="myprogram";
        char *argv[]={file, NULL};
        printf("before exec\n");
        printf("pid : %d, ppid : %d\n",getpid(),getppid());
        printf("session id : %d\n", getsid(getpid()));
        printf("\n");
        execvp(file,argv);
        printf("exec end\n");   //출력되지 않을 것

        return 0;
}

 

이제 이 둘을 컴파일을 하고 다음과 같이 실행시켜보세요. PATH에 현재 디렉토리를 추가하여 myprogram을 실행시켜주도록 합시다. execvp는 PATH에 있는 환경 변수의 경로를 찾게 되니까요. 이렇게 현재 디렉토리를 PATH에 추가하면 보안상 매우 좋지 않습니다. 하지만 이해를 돕기 위해서 진행합니다.

# export PATH=$PATH:.
# gcc myprogram.c -o myprogram
# gcc myexec.c
# a.out
before exec
pid : 500050, ppid : 500036
session id : 499935

after exec
pid : 500050, ppid : 500036
session id : 499935

실행결과는 모두 같습니다.

이 밖에도 exec수행 시에 실행시킨 프로세스의 여러 속성들을 물려받는데, 그중 몇가지를 아래에 기재하였습니다. 

프로세스 그룹 ID
제어 터미널
실제 사용자 ID, 실제 그룹 ID
현재 작업 디렉토리
프로세스 Signal mask
유보 중인 신호들
파일모드 생성 마스크

 

초간단 shell 만들기

프로세스를 생성하는 함수인 fork()와 exec()를 사용하여 간단한 쉘을 만들어볼 수도 있습니다. 아래는 간단한 쉘을 흉내내본 프로그램 코드입니다.

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

#define MAXLINE 256

int main(){
        char buf[MAXLINE];
        pid_t pid;
        int status;
        printf("@ ");
        while(fgets(buf,MAXLINE, stdin) != NULL){
                if(buf[strlen(buf) - 1] == '\n')
                        buf[strlen(buf) -1] ='\0';

                if((pid = fork()) < 0){
                        printf("fork error\n");
                        exit(1);
                }else if(pid == 0){
                        execlp(buf, buf, NULL);
                        exit(127);
                }

                if((pid = waitpid(pid, &status, 0)) < 0){
                        printf("waitpid error\n");
                }
                printf("@ ");
        }
        return 0;
}

# gcc myshell.c
# ./a.out
@ pwd
/home/ubuntu
@ w
 00:03:26 up 9 days, 21:01,  3 users,  load average: 0.00, 0.01, 0.00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
ubuntu   :0       :0               07 3월21 ?xdm?   1:16m  0.04s /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --systemd --session=ubuntu
ubuntu   pts/1    192.168.35.223   23:36    0.00s  0.81s  0.27s sshd: ubuntu [priv]
ubuntu   pts/8    :pts/7:S.0       03 3월22  3days  1.02s  0.69s vi poll_test.c
@

 

위의 쉘은 아주 제약적입니다. 2개 이상의 인자를 넣을 수는 없고, cd로 디렉토리를 이동하려해도 이동해지지가 않습니다. 하지만 exec를 통해 shell을 만들어볼수 있다는 것을 보여준 간단한 예입니다.

이상으로 exec family에 대해서 알아보았고 몇가지 특징도 알아보았습니다. 

반응형
블로그 이미지

REAKWON

와나진짜

,

디렉토리, 파일과 관련한 더 많은 정보와 예제 코드 를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

디렉토리 열기, 읽기, 닫기

디렉토리를 읽는 것은 파일을 읽는것과는 다른 함수들을 사용합니다. 오늘은 관련 함수들 간단히 살펴보고 사용하는 예를 보도록 하겠습니다.

우선 관련 함수를 사용하려면 아래와 같은 헤더파일을 include해야합니다. 

 #include <sys/types.h>
 #include <dirent.h>

 

1) opendir

 DIR *opendir(const char *name);

 

opendir에 디렉토리 이름을 인자로 넣어주게 되면 정상적으로 종료시 DIR 포인터에 그 디렉토리에 대한 포인터가 반환이 됩니다. 에러시 NULL이 반환되고, errno에 에러 번호가 기록이 됩니다.

 

2) readdir

struct dirent *readdir(DIR *dirp);

 

디렉토리의 내용을 읽게 되면 그 디렉토리 안의 디렉토리나 파일이 dirent 구조체 포인터로 반환되게 됩니다. 이 함수는 주로 while문과 같이 사용됩니다.  dirent의 구조체 내용은 아래와 같습니다. 만약 읽을 파일이나 디렉토리가 없으면 NULL을 반환하게 됩니다.

struct dirent {
       ino_t          d_ino;       /* Inode number */
       off_t          d_off;       /* Not an offset; see below */
       unsigned short d_reclen;    /* Length of this record */
       unsigned char  d_type;      /* Type of file; not supported
                                      by all filesystem types */
       char           d_name[256]; /* Null-terminated filename */
};

 

주석만 잘 읽어도 위의 필드들이 무엇을 의미하는지는 알 수 있겠습니다. 

 

3. closedir

int closedir(DIR *dirp);

파일을 열고난 이후 close() 함수로 닫아주듯이 디렉토리 역시 마찬가지입니다. 이 함수가 closedir이고, opendir()에서 반환받은 DIR*를 전달해주면 됩니다. 에러없이 성공적으로 디렉토리를 닫았다면 0이 반환되고, 그렇지 않으면 -1이 반환됩니다. 이 역시 errno를 통해서 에러 번호를 확인할 수 있습니다.

 

디렉토리 내용 읽기

위의 함수들만을 이용해서 디렉토리의 내용들을 볼 수 있습니다. 아래는 그러한 예의 프로그램 코드입니다.

#include <dirent.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
void read_dirent(char *dir_name){

        DIR* dir = opendir(dir_name);
        if(dir == NULL){
                printf("디렉토리를 열수 없습니다.\n");
                return;
        }

        struct dirent *entry;
        while((entry=readdir(dir))!=NULL){
                if(strcmp(entry->d_name,".") == 0 || strcmp(entry->d_name,"..") == 0) continue;
                printf("%s\n",entry->d_name);
        }

        closedir(dir);
}
int main(int argc, char *argv[]){
        if(argc != 2){
                printf("1개의 directory path가 필요합니다. \n");
                return 1;
        }

        read_dirent(argv[1]);
}

read_dirent() 함수는 간단합니다. 먼저 디렉토리의 이름을 인자로 받고 있습니다. 1. 이름을 통해서 opendir을 통해 디렉토리를 열고, 이후 2.read_dir로 반복적으로 그 디렉토리의 내용을 읽습니다. 마지막은 3,closedir로 닫는 역할을 합니다. 

# gcc dir_test.c
# ./a.out /etc/logrotate.d/
ufw
unattended-upgrades
cups-daemon
rsyslog
wtmp
ubuntu-advantage-tools
speech-dispatcher
apt
apport
bootlog
dpkg
ppp
alternatives
btmp

 

재귀적으로 하위 디렉토리까지 읽기

위의 코드를 조금만 바꾸고 lstat을 이용하게 된다면 재귀적으로 호출할 수도 있습니다. lstat에 대해서 모르신다면 아래 포스팅을 참고해보세요.

https://reakwon.tistory.com/40

 

[리눅스] 파일 정보 얻어오기(lstat), 사용 예제

파일 정보 얻어오기 파일에 대한 정보를 얻어올때는 lstat시스템 콜을 사용하면 됩니다. 사실 stat, lstat, fstat등 여러가지 시스템 콜이 있는데요, 가장 간단하게 사용할 수 있는 시스템 콜이기 때문

reakwon.tistory.com

 

#include <dirent.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <sys/stat.h>

void concat_path(char *dest, char* src1, char* src2){
        strcpy(dest,src1);
        strcat(dest,"/");
        strcat(dest,src2);
}
void read_dirent(char *dir_name){

        DIR* dir = opendir(dir_name);
        if(dir == NULL){
                printf("디렉토리를 열수 없습니다.\n");
                return;
        }

        struct dirent *entry;
        while((entry=readdir(dir))!=NULL){
                if(strcmp(entry->d_name,".") == 0 || strcmp(entry->d_name,"..") == 0) continue;
                struct stat buf;
                char full_path[256];

                concat_path(full_path, dir_name, entry->d_name);

                printf("%s\n",full_path);

                if(lstat(full_path, &buf) <0)
                        printf("lstat error\n");
                else{
                        if(S_ISDIR(buf.st_mode))        //디렉토리일 경우 재귀 호출
                                read_dirent(full_path);

                }

        }

        closedir(dir);
}
int main(int argc, char *argv[]){
        if(argc != 2){
                printf("1개의 directory path가 필요합니다. \n");
                return 1;
        }
        //오른쪽에 /가 들어올 수 있음. ex) /etc/ -> /etc
        if(argv[1][strlen(argv[1])-1] == '/') argv[1][strlen(argv[1])-1]='\0';

        read_dirent(argv[1]);
}

 

코드가 길어보이지만, 어렵지 않은 코드입니다. 바로 위의 코드에 비해서 1. 완전한 경로를 구할 수 있는 코드(concat_path)를 추가한것2. 재귀적으로 호출하는 부분이 전부입니다. 이렇게 구현하게 되면 아래와 같이 재귀적으로 하위 디렉토리까지 출력하게 됩니다.

# ./a.out /usr/include
...
/usr/include/linux
/usr/include/linux/nilfs2_api.h
...
/usr/include/linux/netfilter
/usr/include/linux/netfilter/xt_HMARK.h
...

 

이제 디렉토리를 다루는 방법과 lstat에 대해 이해하셨다면 ls의 a, l, i, R 옵션등을 구현해낼 수 있을 것입니다. 그것은 여러분들이 직접 구현해보세요. 어렵지 않습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

전자제품에 쓰이는 중요장치 CPU와 MCU

차이를 좀 쉽게 알아볼까해요.

이미지 출처 :&nbsp;https://www.hackerschool.org/Sub_Html/HS_University/HardwareHacking/04.html

 

CPU(Central Processing Unit) : 중앙처리장치

컴퓨터를 배울때 CPU, CPU 많이 얘기하잖아요? CPU가 중앙처리장치라고 해서 우리의 두뇌같은 역할을 한다고 이런 추상적인 말은 많이 들었을 겁니다. 저처럼 아무 생각이 없는 사람은 논외로 하고 대부분의 사람은 머리가 시키는 대로 팔, 다리가 움직이고, 보고 들으면서 반응을 하게 됩니다. CPU가 바로 이러한 기억과 연산, 제어를 종합적으로 처리하는 장치라고 보시면 됩니다. CPU는 이런 기능을 하고는 있지만, 단독으로는 사용할 수 없습니다. 그래서 메인보드, 주기억장치(RAM), 보조기억장치(하드디스크), 버스 등 주변 장치들이 필요합니다.

CPU를 단일 IC칩에 직접시켜 만든 소자가 MPU(Micro Processing Unit)라고 합니다. 자, 간단하게 이름을 보시면 더 이해하기 편합니다. 아주 작은(Micro) 연산 장치(Processing Unit)이라는 의미로 CPU를 소형화시킨 것이라고 이해하시면 됩니다. MPU는 결국 CPU의 한 종류이지만, CPU가 MPU인것은 아닙니다. 하지만 요즘에는 이 둘을 통용해서 CPU라고 칭합니다. 또 CPU는 프로세서(Processor)라고도 합니다. 

이미지 출처 :&nbsp;https://computechlog.blogspot.com/2017/03/operating-system2.html

 

코어(Core)

이전에는 CPU하나가 모든 일을 전부 담당을 했었죠. 이제는 그렇지 않습니다. CPU안에서도 코어라는 개념을 두게 되었습니다. 코어는 일을 실제 하는 단위라고 보시면 됩니다. 그래서 코어가 두개이면 여러 일을 두 코어가 같이 처리하게 됩니다. 여러분들도 회사에서 혼자 일하는 것보다 두 사람이 일해서 빨리 끝나서 칼퇴해야되잖아요. 그런 개념입니다. 코어가 하나인 CPU는 싱글코어(Single Core)라고 합니다. 듀얼 코어 CPU는 물리적인 CPU 2개가 일하는 것과 비슷하다고 생각하시면 됩니다. 2개의 Core는 듀얼코어(Dual Core), 4개의 코어는 쿼드코어(Quad Core), 8개는 옥타코어(Octa Core)라고 합니다. 물론 클록수가 현저하게 차이나면 결과는 다르지만 코어가 많을 수록 좋습니다(한 사람이 100개의 일을 1시간에 처리하는 것이 2명이 10개의 일을 한시간에 처리하는 것보다 더 빠른 처리가 가능하겠죠.). 

 

MCU(Micro Controll Unit) : 마이크로 컨트롤러

MCU는 작은 컴퓨터라고 해서 one-chip microcomputer 라고 합니다. MCU는 일반 CPU에 비해 작으면서, 구동할 수 있는 최소한의 필수적 기능만을 포함하면서, 그로인해 연결할 수 있는 핀의수도 적습니다. 이렇기 때문에 전력을 적게 쓸 수 있고, 비용도 저렴합니다. 일반적인 CPU와는 다르게 MCU는 이름에서도 알 수 있듯이 칩 안에 메모리, 버스, EEPROM 등 우리가 알고 있는 컴퓨터를 작게 축소해놓은 것과 같습니다. 그래서 매우 작은 컴퓨터(microcomputer)라고 지칭하는 것이겠죠. 이를 줄여서 마이컴(MICOM, micro-computer)라고도 합니다. 그렇기 때문에 기능이 그렇게 많이 정해져있지 않은 작은 전자제품(차량용 ECU 등)에 많이 쓰입니다. 

EEPROM(Electrically Erased Programmable ROM) : 전기적으로 프로그래밍할 수 있는 비휘발성 메모리입니다. 이곳에다가 프로그래밍하여 값을 쓰게 되면 EEPROM에 기억이 됩니다. 비휘발성이라는 뜻은 직접 데이터를 지우지 않는 이상은 데이터가 보존된가는 뜻입니다. 시스템이 꺼져도 유지가 된다는 겁니다. 그렇기 때문에 MCU에서는 EEPROM이 하드디스크를 대체한다고 생각하시면 될 것 같습니다. 물론 대용량은 안되구요. 그리고 사용횟수에 제한이 있다는 점.

아래는 간략하게 CPU와 MCU를 비교한 그림입니다. 이와 같이 CPU가 주기억장치, 보조기억장치 등을 따로 연동시켜야하지만, MCU는 이를 포함하고 있죠. 아주 미니미니하게 포함하고 있다는 것입니다.

이미지 출처 :&nbsp;https://www.hackerschool.org/Sub_Html/HS_University/HardwareHacking/05.html

 

지금까지 CPU와 MCU에 대해서 간단히 알아보았습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,