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

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

와나진짜

,

mount

mount라는 뜻은 '올라타다'라는 영어의 뜻을 가지고 있습니다. 

리눅스에서도 비슷한 의미로 쓰이는 것 같아요. 왜 비슷한 의미로 쓰이는 지는 포스팅을 보시면서 느껴보시기 바랍니다. 

요즘 컴퓨터들보면 CD를 넣었을때나 DVD를 넣었을때, 그리고 USB를 넣었을때 시스템이 자동으로 인지하고 실행시키거나 읽어주죠? 너무 편리합니다. 하지만 이전에 시스템, 혹은 가벼운 전자 제품에 리눅스와 같은 커널을 사용할 경우 시스템이 저절로 인식하지 못할 수도 있습니다. USB나 CD를 넣었을때 인식하고 사용하기 위해서 필요한 명령어가 바로 mount 명령입니다. 간단하게 USB를 mount해보도록 합시다.

USB mount 해보기

0. 먼저 mount를 그냥 치게되면 현재 마운트된 시스템의 하드웨어들이 보이게 될겁니다. 그 중 sda, sdb 등이 디스크라고 보시면 됩니다. 아래는 저의 시스템에 마운트된 sda입니다.

1. 옹골진 USB를 일단 꽂습니다.

2. fdisk -l로 USB 메모리가 꽂힌 블록 디바이스 파일(/dev/ 밑에 있는 파일) 이름을 확인합니다. sda, sdb, sdc,, ... 처럼 앞에 'sd'가 붙는 것이 일반적입니다. 가장 간단하게 확인할 수 있는 것은 새로 생긴 sd*를 보고 용량을 보면 알 수 있습니다. 참고로 USB가 128G 용량이라고 해서 128G 온전히 다 잡히지는 않습니다. 한 116G정도로 약간 적게 잡힙니다.

제 경우에는 sdb1이라는 이름입니다.

3. 아래의 명령으로 mount합니다. 

mount [-t file_system_type] [USB로 인식된 블록 디바이스 파일] [마운트 지점]

앞에 file_system_type은 생략 가능한데, 리눅스가 그 파일 시스템을 지원할때 가능합니다. 그것이 아니라면 명시적으로 지정해주어야합니다. 물론 리눅스 파일 시스템인 ext계열 파일 시스템은 지원가능하지만 윈도우즈(ntfs)의 파일 시스템의 경우나 CD-Rom을 위한 파일 시스템(iso9660)은 지원하지 않을 수 있습니다.

저의 usb의 경우에는 FAT32 파일 시스템을 사용하고, 혹시 지원하는지 확인하기 위해서 그냥 -t 옵션 사용하지 않고 해본 결과 잘되는 것을 확인했습니다.

mount /dev/sdb1 /tmp/usb

 

파일 시스템(File System)이란?

사실 말이 어려워서 File System이지, 간단하게 생각하면 별거 없습니다. 자, 여러분이 책을 정리할때를 생각해보세요. 저같은 경우는 일단 책을 보지 않습니다.  어떤 사람은 책꽂이 맨윗줄은 만화책, 그리고 그 다음 줄에는 전공서적, 그리고 그 다음 줄은 소설책 등으로 카테고리를 정리할 수 있습니다. 그리고 각 카테고리마다 또 사전순으로 책을 잘 정리하겠죠. 그래서 만약 "원피스"라는 만화책을 찾으려면 맨 윗줄, 'ㅇ'으로 시작되는 만화책 부분을 찾으면 되겠군요. 그리고 다 본후에는 다시 원위치에 꽂아서 넣으면 됩니다. 이렇게 책을 관리하는 나름대로의 체계가 있듯이 컴퓨터도 파일을 정리할때 체계가 존재합니다. 그래서 어떻게 파일을 삭제하고, 파일을 기록하고, 찾는지 등을 쳬계화해놓은 것이 File System이라고하며 저장 매체나 OS마다 각기 다른 파일 시스템을 사용하고 있습니다. 여러분이 책 정리할때 반드시 위의 사람과 같이 정리하지는 않듯이 말이죠.

여기서 간략하게 OS마다 어떤 파일 시스템을 갖는지만 살펴보도록 하지요.
Linux : ext, ext2, ext3, ext4, xfs
Windows : FAT12, FAT16, FAT32, exFAT, NTFS
Mac : HFS, HFS+

한가지 자주쓰이는 옵션은 -r옵션인데요. 파일을 오직 읽기(read-only)만 하는 용도로 사용하겠다는 옵션입니다. 예를들어 CD겠죠? CD에 파일을 추가하거나 삭제할 수는 없으니까요. 또한 민감하거나 삭제되지 말아야할 경우에 쓰이기도 합니다. -o ro와 같은 옵션이기도 합니다.

혹시 mount에서 read-only로 마운트된 것을 read, write 가능하게 바꾸려면 아래의 명령을 사용하시면 됩니다. 단, rw가 가능한 파일시스템에 대해서만 입니다. CD-ROM은 당연안됩니다.

mount -o rw,remount [마운트지점]

4. 마운트를 해제할 경우 umount 명령을 사용하여 해제할 수 있습니다.

umount [마운트 지점]

그래서 umount /tmp/usb라는 명령어로 마운트 해제할 수 있습니다.

 

여기서 한가지 의문점이 들지 않나요?

왜 굳이 mount해서 쓰는 거지? 어차피 /dev/sdb1와 같이 자동으로 인식해주는데, 뭐하러 디렉토리를 만들고 mount하고 쓰는 것일까?

여러분이 기억하셔야할 점은 리눅스에서 모든 장치들은 파일로 취급한다는 점입니다. sdb 역시 파일로 인식되지요. 그래서 'cd /dev/sdb'와 같은 명령으로 디렉토리같이 사용할 수 없다는 것입니다. 또 어떻게 파일을 추가하고, 파일을 추가할때 이름은 몇자까지 제한이 되며, 파일을 삭제할때는 어떻게 삭제하는지도 모릅니다. 이러한 동작 방식들은 파일 시스템에서 정의하고 있기 때문입니다. 그렇기 때문에 우리는 파일 시스템을 명시하여 파일의 동작(operation)을 알려주면서 USB를 사용할 수 있게 됩니다(물론 아까도 말씀했다시피 지원되는 파일 시스템은 명시적으로 지정해주지 않아도 알아서 파일 시스템을 찾아줍니다.).

그래서 mount 명령어로 인식된 sdb에 대해서 파일이 동작하는 방식은 파일 시스템을 딱 알려주고 이것을 /tmp/usb 디렉토리에 얹어 쓰겠다고 리눅스에게 말해주는 것이죠. (아래 그림에서 sdb가 아니고 sdb1입니다.)

 

반응형
블로그 이미지

REAKWON

와나진짜

,

리눅스의 특수 권한(setuid, setgid, sticky) 외에 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

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

reakwon.tistory.com

 

실행권한

리눅스에서 파일을 다루는 방법은 세가지가 있습니다. 파일을 읽고(read), 쓰고(write), 실행(execute)하는 것이 그 세가지입니다. 리눅스는 서버용 멀티유저 운영체제이기 때문에 권한이 매우 중요합니다. 어떤 관리자는 특정 파일에 대해서 읽고 쓸 수 있는 권한이 있을 수 있고, 다른 관리자는 수정이 불가한 파일이 있을 수가 있겠죠. 이렇게 파일을 다룰 수 있게 리눅스에서는 파일의 속성을 줄 수가 있습니다. 

ls -l 명령으로 exam.txt파일의 실행권한을 보도록 하겠습니다.

노란색으로 표시한 부분이 이 파일의 권한을 의미합니다. 이 파일을 만든 소유자는 ubuntu이고 ubuntu라는 그룹이라는 것도 알 수 있습니다.

-rw-rw-r--

- r w - r w - r - -
파일 종류
-는 일반 정규 파일
소유자의 read 권한 소유자의 write권한 소유자의 실행권한 X 소유자 그룹의 read권한 소유자 그룹의 write권한 소유자 그룹의 실행권한X 다른 사용자의 read권한 다른 사용자의 write권한X 다른 사용자의 실행권한X

 

맨앞의 파일의 종류를 나타내는 '-'를 제외하고 권한의 '-'는 그 파일에 대한 해당 권한이 없다는 것을 의미합니다. 권한은 아까 세종류가 있다고 했는데 각각 이렇습니다.

r read로 파일을 읽을 수 있는 권한입니다.
w write로 파일을 수정할 수 있는 권한입니다.
x execute로 파일을 실행할 수 있는 권한입니다. 파일에는 단순 기록하는 파일외에도 실행파일이 있기 때문에 이러한 권한이 필요합니다.

 

chmod 명령어

chmod는 파일의 권한을 바꿀 수 있는 명령어입니다. 명령어 형식은 이렇습니다.

chmod [파일에 추가거나 뺄 권한] [파일 이름]

만일 다른 유저들의 쓰기 권한을 추가하고 싶다면 아래의 명령으로 권한을 추가할 수 있습니다. 

 

u+w의 앞 u는 사용자를 의미합니다. 여기서 +는 더한다는것을 알 수 있겠죠? 반대로 뺄때는 -를 씁니다. 마지막 글자 w는 어떤 권한인지를 말합니다. 읽기 권한을 추가하려면 r를 사용하면 되겠네요. 맨 처음 글자는 아래와 같습니다. 

u user의 앞글자로 소유자를 의미합니다.
g group의 앞글자로 소유자의 그룹을 의미합니다.
o other의 앞글자로 다른 유저들을 의미합니다.

 

여러 권한을 설정할때는 쉼표로 나열해주면 됩니다. 아래는 그룹과 다른 유저들에게 r,w를 더해주는 명령어의 예입니다.

chmod g+rw,o+rw file.txt

그리고 숫자로 권한을 일괄적으로 바꾸는 방법도 있습니다.

파일의 권한을 설명할때 rwxrwxrwx로 해도되지만 보통은 정수를 사용하여 권한을 이야기합니다. 앞에 rw-rw-r--는 숫자 664로 대응이 되는데, 왜 이렇게 되는걸까요? 세개를 묶어서 세비트로 표현하기 때문입니다.

r w - r w - r - -
1 1 0 1 1 0 1 0 0
4 2 0 4 2 0 4 0 0

두번째 줄은 이진수, 세번째 줄은 10진수로 표현했습니다. 그래서 rw-는 결국 이진수 110으로 되어 6이 됩니다. 그렇다면 rwx는 111이 되어서 7이겠네요.

아래는 chmod로 유저는 모든 권한을, 그룹 사용자는 읽기, 쓰기 권한을, 그리고 다른 사용자는 읽기 권한만 추가하는 명령어의 예입니다.

- chmod 764 a.out

그리고 setuid와 setgid, sticky의 비트를 사용하여 네자리로 표현할 수도 있습니다. 각각 setuid는 4, setgid는 2, sticky는 1로 대응이됩니다.

- chmod 4764 a.out

위 명령은 setuid를 설정하는 명령입니다. setuid와 setgid, sticky는 아래에 설명하도록 하겠습니다.

 

setuid

setuid를 설명하기에 앞서 리눅스에서는 유저를 id로 구별합니다. 두 종류가 있는데 아래와 같습니다.

UID (REAL UID) : 이는 실제 사용자 본인의 아이디를 표현한다고 해서 real id라고도 하면 uid라고 합니다. 

EUID (EFFECTIVE UID) : 유효 사용자 아이디라고해서 프로그램이 실행될때 갖는 아이디를 말합니다. 즉, 실행시에 이 프로그램을 만든 사용자의 ID로 실행이 된다는 겁니다. 

setuid의 예1)

그렇다면 setuid의 예를 하나 들어보도록 할까요? 아래와 같이 root가 파일 하나를 만들고 본인만 읽을 수 있게 만들어 놓았습니다.

 

그리고 root 사용자는 아래와 같은 코드로 이 파일을 읽는 코드를 짜서 실행파일을 만들었습니다.

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

int main(){
        int fd=open("root_read_only.txt",O_RDONLY);
        int n;
        char buf[128];
        printf("uid:%d\n",getuid());    //실제 사용자 ID
        printf("euid:%d\n",geteuid());  //이 프로그램을 실행할때만 갖는 ID
        n=read(fd,buf,sizeof(buf));
        if(n<0){
                printf("파일을읽을 수 없습니다. erorr code:%d\n",n);
                exit(1);
        }
        printf("file content:%s\n",buf);
}

 

root 사용자가 이 실행파일의 주인이고 실행파일의 권한은 모든 사용자가 실행할 수 있게 만들었습니다. 

 

이제 다른 사용자인 ubuntu가 이 read.out이라는 실행파일을 실행하게 되면 아래의 내용과 같이 나옵니다. 지금 현재 uid는 1000이고 euid도 1000이라서 root_read_only.txt를 볼 수 없습니다. 왜냐면 root_read_only.txt는 오직 root만 읽을 수 있게 설정했거든요. 

 

이때 이 실행파일이 실행될때만 루트의 권한을 갖도록 하여 파일을 읽을 수 있게 하는 방법은 euid를 root로 설정하는 setuid를 주면 될텐데요. root 계정으로 그 권한을 줘보도록 하겠습니다. chmod u+s read.out으로 setuid를 줄 수 있고, 이때 rwsrwxrwx로 바뀌게 된것을 알 수 있습니다. 소유자의 실행권한인 s로 바뀐 것은 setuid가 설정되어 있는 파일이며 실행시에 파일의 소유자의 권한으로 실행된다는 것을 의미합니다.

 

이제 ubuntu라는 유저는 이 root_read_only.txt라는 파일을 읽을 수 있을까요? 그럴 수 있는지 read.out을 실행해보도록 합시다. 

 

euid가 0으로 바뀐것을 확인할 수 있으면서 file의 내용을 읽어볼 수 있습니다.

setuid의 예2)  passwd

setuid를 설명하기 위해서 임의로 제가 만든 하나의 예입니다. 리눅스에서 가장 대표적으로 setuid를 사용하는 실행파일은 /bin/passwd파일입니다. 이 파일은 사용자의 비밀번호를 바꾸는데, 필요한 명령어로 /etc/passwd를 수정해야합니다. 하지만 /etc/passwd는 절대 root만 수정할 수 있으므로 다른 사용자는 변경할 수가 없죠. 그렇지만 다른 사용자들이 비밀번호를 바꿀때 /etc/passwd를 수정해야하므로 수정 프로그램이 필요하고 그 파일이 바로 /bin/passwd파일입니다. /bin/passwd는 실행시에 루트권한으로 실행이되어 /etc/passwd파일을 수정할 수 있게 됩니다.

 

 

setgid

setgid 역시 비슷합니다. 실행시에 그룹의 권한을 갖는 다는 것인데요. 앞서 설명한 setuid와 개념은 비슷하며 그룹 권한에 s로 표시가 됩니다. 

 

sticky비트

sticky비트는 다른 사용자가 자유롭게 디렉토리를 사용할 수 있도록 만드는 권한입니다. 원래 디렉토리도 디렉토리를 만든 사람만이 읽기, 쓰기가 가능합니다. 하지만 sticky를 쓰면 모든 사용자가 자유롭게 읽기, 쓰기가 가능합니다. 마치 공유폴더와 아주 비슷한 개념입니다. 이 권한은 디렉토리에만 해당되는 권한입니다. 예를 들어설명해볼까요?

아래와 같이 root는 공유폴더를 만들 목적으로 디렉토리를 하나 만들었습니다. 하지만 권한을 주지는 않았죠. 

 

그래서 ubuntu라는 유저는 이 디렉토리에 파일을 기록하려고 했으나 아래와 같이 권한이 없다는 메시지를 받게 됩니다.

 

아래와 같이 root는 다른 유저에 대해서 sticky비트를 설정합니다.

 

이렇게 되면 아래와 같이 ubuntu는 /shared 디렉토리를 자유롭게 이용할 수 있습니다.

 

여기까지 리눅스 파일의 실행권한과 setuid, setgid, sticky에 대한 개념을 알아보았습니다. 최대한 쉽게 예를 들어 설명하려고 했는데, 이해가 가셨는지 모르겠네요. 

반응형
블로그 이미지

REAKWON

와나진짜

,

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

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

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

reakwon.tistory.com

 

grep

grep 명령어는 각 파일에서 패턴(Pattern)을 검색할때 사용하는 명령어로 리눅스에서 매우 자주 사용하는 명령어입니다. 주로 사용하는 방법은 아래와 같이 나뉘어집니다.

grep [OPTION...] PATTERNS [FILE...]
grep [OPTION...] -e PATTERNS ... [FILE...]
grep [OPTION...] -f PATTERN_FILE ... [FILE...]

 

1. 첫번째 방식 : 옵션과 함께 PATTERNS를 주어진 파일들에서 찾습니다.

2. 두번째 방식 : 옵션과 함께 정규표현식으로 PATTERNS을 정의하고 주어진 파일들에서 찾습니다.

3. 세번째 방식 : 옵션과 함께 파일에 정의된 PATTERN을 주어진 파일들에서 찾습니다.

 

FILE을 지정하는 방법에 따른 검색

FILE은 찾을 대상의 파일을 의미합니다. 찾을 파일들을 명시적으로 지정하게 되면 그 파일만들 탐색합니다. 만약 -(대쉬, dash)이면 stdin에서 찾습니다. 즉, 키보드로 입력이 되면 패턴을 찾는다는 방식입니다. 만약 파일이 주어지지 않았다면 -r 옵션에 따라 다릅니다. -r 옵션이 주어지면 현재 디렉토리에 있는 파일들을 대상으로 하위 디렉토리까지 패턴을 찾습니다. 그렇지 않는다면 표준 입력(stdin)으로 입력받습니다.

이러한 예를 들어보도록 하겠습니다.

ex ) FILE을 명시 

현재 디렉토리 /etc이며 여기서 shadow, passwd 파일에서 root 패턴을 포함하는 라인을 출력합니다.

# pwd
/etc
# grep "root" shadow passwd
shadow:root:$6$Mg4AK6u6xv/EZk93$vwpv6ALfAv1jH0.3Ub0hIjVgDXJSa9Mwk625jfzNzYzR48aypIFflbhUL4VAxECs3PiFr6NTA0ghnAfpAj2Bz1:18693:0:99999:7:::
passwd:root:x:0:0:root:/root:/bin/bash
passwd:nm-openvpn:x:118:124:NetworkManager OpenVPN,,,:/var/lib/openvpn/chroot:/usr/sbin/nologin

 

ex) FILE을 "-"로 지정

이렇게 파일대신 대시를 사용하게 되면 표준입력으로 입력된 것에서 패턴을 검색합니다. Apple과 일치하게 되면 색이 달라지는 것을 볼 수 있습니다.

# grep "Apple" -
Apple
Apple
goo
foo
boo
Apple
Apple
apple
^C

 

ex) FILE 지정 생략

FILE 생략시 역시 키보드로 입력받는 데이터 중에서 검색을 합니다. 파일을 '-' 준것과 동일함을 알 수가 있네요.

# grep "Apple"
Apple
Apple
Banana
App
Application
Apple
Apple
^C

 

ex) -r 옵션과 FILE 지정 생략

현재 디렉토리부터 하위 디렉토리에 있는 파일까지 전부 검색합니다. 여기서는 "root"라는 패턴이 포함되면 그 라인을 전부 출력하게 됩니다.

# pwd
/etc
# grep "root" -r
ca-certificates.conf:mozilla/Comodo_AAA_Services_root.crt
shadow:root:$6$Mg4AK6u6xv/EZk93$vwpv6ALfAv1jH0.3Ub0hIjVgDXJSa9Mwk625jfzNzYzR48aypIFflbhUL4VAxECs3PiFr6NTA0ghnAfpAj2Bz1:18693:0:99999:7:::
passwd_bak:root:x:0:0:root:/root:/bin/bash
passwd_bak:nm-openvpn:x:118:124:NetworkManager OpenVPN,,,:/var/lib/openvpn/chroot:/usr/sbin/nologin
xattr.conf:xfsroot.*                    skip            # xfs specific; obsolete
nanorc:## In root's .nanorc you might want to use:
...//생략//...

 

지원하는 옵션들의 예제

grep은 많이 쓰이는 명령어인 만큼, 매우 많은 옵션을 지원하고 있습니다. 전부 외우고 숙지하는 것은 무리이니까 자주 사용하고 유용한 옵션들만 알아보도록 하겠습니다.

옵션 설명
-r recursive의 약자로 현재 디렉토리부터 하위 디렉토리까지 전부 탐색합니다.
-i ignore case의 약자로 패턴의 대,소문자를 구분하지 않고 검색합니다.
-c 파일에서 지정한 패턴과 얼마나 일치하는지에 대한 수(count)가 출력됩니다.
-n 파일의 어느 라인에서 패턴이 일치하는지 라인 넘버(number)도 같이 출력해줍니다.
-l 패턴과 일치하는 파일의 이름만을 출력합니다.
-f 파일에 있는 패턴으로 검색합니다.
-e 정규 표현식으로 패턴을 검색합니다.
-v 패턴과 일치하는 라인을 삭제합니다. 불필요한 라인을 삭제할때 유용합니다.

 

 

grep 사용 예제

1) grep -irn "<stdio.h>" : stdio.h 헤더 파일을 사용하는 파일을 현재 디렉토리부터 검색하여 line도 같이 출력

# grep -irn "<stdio.h>"
test.c:1:#include <stdio.h>

 

2) 명령어의 결과를 grep으로 검색

grep은 기본적으로 파일을 지정하지 않는다면 stdin으로 입력을 받는다고 위에서 설명을 했었습니다. 리눅스에서 파이프는 어떤 명령어의 출력(정확히 이야기하면 표준 출력, stdout)을 다음 명령어의 stdin으로 넘겨줍니다. 그래서 이런 명령어 형식을 자주쓰게 됩니다.

명령어| grep "찾을 패턴"

 

예를 들면 현재 실행되는 프로세스 중에 ssh와 관련된 프로세스를 알아보고 싶다면 아래와 같은 조합으로 사용할 수 있습니다. 

# ps -ef | grep "ssh"
ubuntu      2032    1955  0  8월24 ?      00:00:03 /usr/bin/ssh-agent /usr/bin/im-launch env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --systemd --session=ubuntu
root      108345       1  0  8월25 ?      00:00:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
root      275674  108345  0 20:45 ?        00:00:00 sshd: ubuntu [priv]

 

ps 명령어는 결과를 stdout으로 출력하기 때문에 이 출력 결과를 grep으로 넘겨줍니다. 전달받은 표준 출력은 grep의 표준 입력으로 전해져 "ssh"의 패턴과 일치하면 출력하게 됩니다. 이해가 되시나요?

만약 pipe와 관련한 설명이 부족하다면 아래의 링크를 통해서 더 자세히 알아보세요.

https://reakwon.tistory.com/115

 

[리눅스] 재지정, 리다이렉션(redirection: >, <)과 파이프(|) 개념과 쉬운 설명

재지정(Redirection) 리눅스에서 프로그램은 보통 세 개의 파일 서술사를 열게 됩니다. 바로 표준 입력(standard input, STDIN), 표준 출력(standard output, STDOUT), 그리고 표준 에러(standard error, STDERR)..

reakwon.tistory.com

 

3) # grep -v "root" /etc/passwd : 패턴 root이 없는 라인만 검색

# grep -v "root" /etc/passwd
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin

 

여기까지 grep에 대한 기본적인 원리와 자주 사용하는 사용법을 알아보았습니다. 정규표현식을 아시는 분은 더욱 유용하게 사용할 수 있겠지만 저는 거기까지 필요는 없이 잘 사용하고 있습니다.

 

반응형
블로그 이미지

REAKWON

와나진짜

,

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

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

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

reakwon.tistory.com

 

ps(process status)

여러분들이 컴퓨터를 사용할때 가장 많이 사용하는 명령이 있습니다. 그 중 한가지 경우가 프로세스에 대한 정보를 알고 싶을 경우인데요. 예를 들어 어떤 프로세스가 수행중인데, 이 프로세스가 CPU를 굉장히 많이 소모시킨다고 합니다. kill 명령으로 그 프로세스를 종료시키고 싶지만 pid를 알아야하겠죠. 이때 pid등의 정보를 볼 수 있는 명령이 있습니다. 그 process의 상태를 알고 싶을때 매우 많이 사용하는 명령이 바로 ps입니다.

 

ps는 프로세스에 대한 많은 정보를 담고 있고 매우 많이 사용되는 명령으로 옵션이 매우 다양합니다. ps는 옵션에는 세가지 종류가 있습니다.

1. Unix Option : 앞에 '-' (dash)가 붙는 옵션 표기방법입니다. 

2. BSD Option : '-' 를 붙이지 않습니다.

3. GNU Option : 명령어 앞에 '--' (double dash)를 붙입니다.

물론 Unix, BSD, GNU 옵션들을 모두 우리가 알 수도 없고, 외울 수도 없지만 그래도 유용한 옵션정도는 알고 있어야겠죠? 

 

기본 ps 명령어 구성

ps 명령어를 치면 아래와 같이 나오게 됩니다.  별로 몇개 나오지 않는 다는 것을 알 수 있는데, 이것은 ps가 기본적으로 같은 EUID(Effective User ID)의 프로세스이며 같은 터미널의 프로세스만을 골라서 보여주기 때문입니다. 

보여주는 정보는 프로세스 ID(PID), 터미널(TTY), CPU 점유 시간(TIME), 그리고 프로세스가 수행된 명령어(CMD)입니다. 여기서 a.out은 제가 임의로 무한 루프를 돌린 프로세스를 실행시켜서 그렇고 시간은 TIME이 00:04:31인것을 볼 수 있네요.

 

ps -e, -A : 모든 프로세스를 보여줍니다. 

ps -a : 세션 리더와 터미널과 연관된 프로세스들을 제외한 모든 프로세스를 보여줍니다.

ps -d : 세션 리더를 제외한 모든 프로세스를 보여줍니다.

ps -f : full format으로 세션의 정보를 표시합니다. 

ps -ef : -e와 -f의 옵션 조합인데, 모든 프로세스를 full format으로 보여줍니다. 아래는 그 결과를 보여줍니다. UID, PID, PPID, C, STIME, TTY, TIME, CMD의 정보를 볼 수 있네요. TTY(연결 터미널)가 없으면 대부분 데몬 혹은 커널 프로세스입니다. 

ps -ef 결과1
ps -ef 결과 2

 

 

ps -u userlist : EUID 혹은 유저 이름으로 프로세스를 고릅니다. 이때 여러 uid를 줄수 있는데 ','(comma)로 구분하여 명시해줍니다. euid는 프로세스가 수행할때 갖는 유저 권한을 말합니다.

ps -U userlist : -u 옵션과는 동일하나 RUID가 갖는 프로세스만을 찾아냅니다. ruid는 real user id라는 것으로 실제 프로그램을 실행한 uid를 의미합니다. 이때도 쉼표로 여러 uid를 지정할 수 있습니다.

ps -p pidlist : 프로세스 id가 일치하는 프로세스를 출력합니다. 여러 pid들을 뽑아내고 싶다면 마찬가지로 ','(comma)로 pid를 구분하여 명시해줄 수 있습니다. 이 명령은 ps --pid pidlist와 같습니다.

ps --ppid pidlist : 부모 프로세스 id와 일치하는 프로세스를 출력합니다. 역시 여러 ppid를 ','(comma)로 구분가능합니다.

ps -t ttylist : tty와 일치하는 프로세스들을 출력해줍니다. 이 명령은 t 혹은 --tty 옵션과 같습니다. 

ps -o format : 사용자가 지정한 format대로 출력합니다. format에 대해서는 설명이 길지만 간략하게 원하는 column만 보여준다고 기억하시면 됩니다. 예를 들어 사용자가 임의로 pid, ppid, cmd, uid 등을 표시할 수 있습니다.

다음의 표는 format에 대해 정리한 표입니다.

CODE NORMAL HEADER
%C pcpu %CPU
%G group GROUP
%P ppid PPID
%U user USER
%a args COMMAND
%c comm COMMAND
%g rgroup RGROUP
%n nice NI
%p pid PID
%r pgid PGID
%t etime ELAPSED
%u ruser RUSER
%x time TIME
%y tty TTY
%z vsz VSZ

 

아래의 예는 uid가 0, 1000인 프로세스를 출력하는데, uid, ruid, euid, guid, pid, ppid, cmd를 출력해줍니다.

ps -u userlist -o format

 

ps aux : BSD 문법으로 실행중인 모든 프로세스를 나타냅니다. ps -aux와는 다른 옵션입니다. 

 

ps aux

 

위의 보이는 것 중에 프로세스의 상태를 나타내는 STAT 혹은 S는 아래의 코드로 구성됩니다. 다른건 필요없고 Z나 <defunct>로 표시된 프로세스는 좀비 프로세스로 자원을 점유하므로 시스템 관리가 필요합니다. 반드시 없애야합니다.

Code Desc
D Uninterruptible sleep
Idle Idle kernel thread
R Running or runnable
S Interruptible sleep
T stopped by job control signal
t stopped by debugger during tracing
W paging
X dead
Z defuct (zombie) process

 

BSD format에서는 추가 문자가 쓰일 수 있습니다.

Character Desc
< high-priority(not nice to other users)
N low-priority(nice to other users)
L has pages locked into memory
s is a session leader
l is multi-threaded
+ is in the foreground process group

 

ps | grep - 원하는 프로세스만 추출

대개 ps명령은 많은 결과를 출력하는데 이때 grep을 이용하여 원하는 프로세스를 찾을 수 있습니다. 예를 들면 sshd와 관련된 프로세스를 찾기를 원하면 이렇게 사용할 수 있습니다.

ps -ef | grep sshd

 

ps -ejH

프로세스를 트리 형태로 조금 보기 좋게 표시하고 싶다면 -ejH옵션을 사용하면 됩니다. 자식 트리면 CMD가 한칸 띄어져서 출력이 됩니다.

ps -ejH 1
ps -ejH 2

 

사실 트리모양으로 보기 좋게 출력하고 싶다면 아래의 pstree 명령이 더 보기 좋습니다.

pstree

여러분이 트리 형식으로 실행중인 프로세스를 보고 싶으시면 pstree 명령을 사용하시면 됩니다. 이 트리는 기본적으로 init 혹은 systemd 프로세스가 루트인데, 만약 pid를 명시한다면 그 pid가 루트가 됩니다. 

pstree

 

이상으로 ps명령어와 옵션에 대해서 포스팅을 했습니다. 사실 모든 옵션을 사용할 일은 없습니다. 저는 ps -ef | grep 만 사용하게 되던데, 저도 나중에 참고할 겸 포스팅을 했습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

 

 

1. 커서 이동

$ : 커서를 줄 맨끝으로 이동합니다.

0 : 숫자 0을 누르면 그 줄의 맨 앞으로 커서가 이동합니다. 빈칸을 포함하여 맨 앞으로 이동합니다.

^ : 0과 같이 줄 맨 앞에 커서를 위치하지만 글자앞으로 이동합니다.

ex)

                               reakwon.tistory.com
ㄴ 0을 누를때 커서             ㄴ^를 누를때 커서

 

+ : 커서를 다음줄의 첫 글자로 이동합니다.

- : 커서를 이전 줄의 첫 글자로 이동합니다.

gg : 커서를 문서의 맨 처음 줄로 위치합니다.

G : 커서를 문서의 맨 마지막 줄으로 위치합니다.

w : 다음 단어의 첫 글자로 이동시킵니다. 특수문자 발견시 멈춥니다.

W : 다음 단어의 첫 글자로 이동시킵니다. 단어를 공백으로 구분합니다. 따라서 공백 이후를 다음 단어로 취급합니다.

b : 단어의  첫글자로 이동시킵니다. 특수문자 발견시 멈춥니다.

B : 단어의 첫글자로 이동시킵니다. 단어의 구분을 공백을 기준으로 합니다.

% : 짝이 되는 괄호의 위치까지 이동합니다. 만약 여는 괄호 '('에서 %을 누르면 짝맞는 ')'까지 커서가 이동합니다. 만약 선택이 된 상태에서 %를 누르면 그 괄호내용 모두를 선택할 수 있습니다. 이 기능을 폴딩할때 이용이 될 겁니다.

 

2. 입력

명령에서 대문자 명령을 소문자 명령의 반대의 기능을 하게 됩니다.

a : 현재 커서의 다음에 글자를 입력할 수 있습니다.

A : 현재 줄의 맨 끝에 글자를 입력할 수 있습니다.

i : 현재 커서에서 글자를 입력할 수 있습니다.

I : 현재 줄의 맨 앞에 글자를 입력할 수 있습니다.

o : 커서의 아랫줄에 입력할 수 있습니다.

O : 커서의 윗줄에 입력할 수 있습니다.

 

3. 화면 이동

Ctrl + f : 한 화면 다음(아래)으로 이동합니다. 보다 빠른 이동을 할때 좋습니다.

Ctrl + b : 한 화면 이전(위)으로 이동합니다.

 

4. 선택

v : 기본적으로 v를 누르고 방향키로 텍스트를 선택합니다.

 

 

 

 

4. 삭제, 되돌리기

d : 선택 영역을 삭제합니다. 이떄 선택은 위에 등장한 v로 선택합니다.

dw : 현재 커서부터 다음 단어(공백 포함, 특수문자 제외)까지 모두 지웁니다. 이것을 응용하면 bdw가 왜 한 단어를 삭제하는지 알 수 있습니다.

bdw : 현재 단어 삭제 (b - 단어 앞으로 이동, dw : 다음 단어까지 삭제)

dd : 한줄 전체를 삭제합니다.

ndd : n개의 줄을 한번에 삭제시킵니다.

u : 만약 잘못 삭제시켰다면 u를 눌러서 되돌릴 수 있습니다. 삭제 뿐만 아니라 모든 명령에서 실수했다 싶으면 u로 되돌리시면 됩니다.

 

5. 복사 & 붙여넣기 

y : 선택한 영역을 복사합니다. 선택은 v로 선택할 수 있습니다.

yw : 현재부터 한 단어의 끝까지 복사할 수 있습니다.

byw : 현재 커서가 위치한 이 단어를 복사합니다.

yy : 한줄 복사를 할 수  있습니다.

nyy : n줄 복사

p : 현재 커서 아랫줄에 복사한 텍스트 붙여넣기

 

6. 단어 찾기

/찾을단어 

n : 다음 단어 찾기

N : 이전 단어 찾기

이 방법은 아주 단순하긴한데 정확히 그 문자와 일치하는 단어만 검색합니다. 즉, 대소문자를 구분해서 검색하는 방법입니다. 대소문자 구분없이 검색하기를 원한다면 끝에 \c를 붙여주면 됩니다. 이렇게요

/찾을단어\c

이때는 대소문자 구분없이 찾을 수 있습니다.

 

 

 

 

7. : 기본 명령어 - ESC를 누르고 ':'를 누른 상태

: set nu - 줄 표시를 합니다. 코드에서 라인을 확인하고 싶다면 아래의 명령으로 볼 수 있습니다. 혹은 set number를 full로 쳐주어도 됩니다.

: set nonu - 줄 표시를 해제합니다. 더 이상 라인을 보고싶지 않으면 이 명령으로 해제할 수 있습니다.

: 숫자 - 한번에 라인을 이동하고 싶으면 숫자를 치고 이동할 수 있습니다.

: w - 지금 수정된 사항을 write한다는 것으로 저장을 의미합니다. 하지만 vi 프로그램을 닫지는 않습니다.

: wq - 수정된 사항을 저장하고 나가겠다(quit)는 명령입니다. vi 프로그램을 저장함과 동시에 닫는 명령입니다.

: q! - 어떨때는 vi 편집기가 수정했는데 저장하지 않는다는 이유로 놓아주지 않는 경우가 있는데 이떄 저장하기 싫다면 q!를 사용하면 됩니다.

: set ts=n - 탭의 간격을 조절하는 명령어로 n은 숫자입니다. 예를 들어 set ts=4인 명령을 내린다면 탭의 간격은 공백 4개와 같은 간격을 갖습니다.

 

창분할 - 2가지가 존재합니다.

: sp - 현재 창을 평행 분할합니다.  다음 창은 Ctrl+w,w (Ctrl 누른 상태에서 w를 두번 누름)로 이동할 수 있습니다.

: sp [filename] : 파일 이름을 지정해주면 그 파일이 열립니다.

: vs - 현재 창을 수직 분할 합니다. 마찬 가지로 다음 창은 Ctrl+w,w로 이동할 수 있습니다.

: vs [filename] : 마찬가지로 파일 이름을 지정해서 현재 파일이 아닌 다른 파일을 열 수 있습니다.

 

8. 단어 변경 

:s/현재단어/바꿀단어

현재 단어와 일치하는 하나의 단어만을 변경합니다.

만약 문서 전체의 단어를 변경하고 싶으시면 앞에 %를 붙여주면 됩니다.

:%s/현재단어/바꿀단어

:%s/현재단어/바꿀단어/g

전체 문서에서 현재단어를 바꿉니다.

 

:시작 줄 번호, 끝 줄 번호s/현재단어/바꿀단어

혹은 줄번호를 주어 제약을 걸어서 시작 줄, 끝 줄에 있는 단어를 바꿀 수도 있습니다.

 

:%s/현재단어/바꿀단어/i

현재 단어의 대소문자를 무시하고 일치하면 단어를 치환합니다. i는 ignore를 의미하지요.

 

간혹가다가 위도우즈의 파일을 리눅스로 가져오면 ^M문자가 섞여 들어올때가 있습니다. 이때 ^M문자를 없애려면 아래와 같은 명령을 통해 깔끔하게 없앨 수 있습니다.

%s/^M$//g

 

 

 

 

 

9. 탭 추가

: tabnew [filename] - 현재 vi 창에 새로운 tab을 생성합니다. 기존의 파일을 열 수도 있고 아니면 새로운 파일을 열어서 편집할 수도 있습니다. 예를 들어 list.txt이라는 파일을 탭으로 생성하려면 아래의 명령을 통해서 가능합니다.

 

원래 열었던 파일(test)과 새로운 파일인 list.txt가 같이 열려있다는 것을 알 수 있고, 편집도 가능한 것을 알 수 있습니다.

 

gt : 다음 탭으로 이동합니다. 위의 경우에는 다음 탭이 없으므로 test 탭으로 이동합니다.

gT : 이전 탭으로 이동합니다. 위의 경우에서 이전 탭은 test이므로 test 탭으로 이동하지요.

탭 안에서 편집은 이제 vi 편집기의 명령어를 가지고 똑같이 편집하고 저장할 수가 있습니다.

 

10. 코딩에 도움이 되는 몇가지 명령어

헤더파일 열어 확인하기

헤더파일에 ctrl+wf를 누르면 헤더파일의 내용을 창분할로 볼 수 있습니다. 아래와 같이 stdio.h의 내용을 보고 싶으면 커서를 헤더파일 이름에 위치시키고 ctrl 키와 w,f 를 차례대로 누르면 됩니다.

 

 

폴딩하기

코드가 너무 길어 보기좋게 내용을 접고싶다면 폴딩기능을 사용할 수 있습니다. 아래처럼 v로 함수 모두를 선택해봅시다. v를 누르고 화살표로 선택할 수 있지만 함수가 긴 경우에는 다음과 같이 한번에 선택할 수 있습니다.

우선 함수 가장 앞에 'v'를 눌러 선택 모드로 지정하시구요. '$'를 눌러 가장 맨 끝 줄로 이동합니다. 가장 맨 끝 줄에는 함수를 여는 중괄호가 있습니다. 이때 '%'를 누르면 함수 전체를 선택하게 됩니다.  

 

 

 

함수 선택

zf : 폴딩하려면 함수가 모두 선택된 상황에서 zf를 누르면 됩니다.

 

zi : 폴딩된 상태에서 함수를 보고 싶어서 피거나, 아니면 핀것을 다시 접으려면 zi를 입력하면 됩니다. 이 명령어는 토글방식으로 접었다 폈다할 수 있습니다.

 

한번에 들여쓰기하기

여러분이 코드를 복사하다가 보면 어쩌다가 들여쓰기가 안된 상태로 복사가 될 때도 있습니다. 이때 노가다로 탭을 눌러서 늘리면 시간이 꽤나 걸리겠죠. 이때 한번에 들여쓰기하는 방법이 "gg=G"입니다.

커서가 어디에 있건 상관없습니다. gg는 문서의 맨앞을 의미, G는 문서의 끝을 의미합니다. =은 tab의 간격으로 들여쓰기 하라는 명령이죠. 즉, 문서의 처음부터 끝까지 들여쓰기하는 명령어입니다.

문서 전체 들여쓰기

 

자동 완성

다른 IDE를 사용할때 자동완성 기능이 있죠? "vi 편집기는 왜 그런게 없는거야" 라고 하시지 마시고 구글에서 그런 기능을 하는 명령어가 무엇인지 찾아보세요. 답은 ctrl + p , ctrl + n입니다. 둘 다 자동완성 기능을 하지만 어느쪽으로 펼쳐지느냐만 다를 뿐입니다. 문자를 쓰는 편집 모드에서 ctrl + p, ctrl + n을 사용해야합니다. esc 누르고 사용하는게 아닙니다.

vi 자동완성 기능

 

반응형
블로그 이미지

REAKWON

와나진짜

,